Update: 2026-07-04 18:43:56
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// account_manager.php
|
||||
// Handles rotation and selection of social media accounts
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../../core/bootstrap.php';
|
||||
|
||||
class AccountManager {
|
||||
private $con;
|
||||
|
||||
public function __construct() {
|
||||
$this->con = Database::get('main');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an available account for a specific platform that hasn't been used too recently.
|
||||
*
|
||||
* @param string $platform 'facebook' or 'instagram'
|
||||
* @param int $cooldownMinutes Minimum minutes since last active
|
||||
* @return array|null Account details or null if none available
|
||||
*/
|
||||
public function getAvailableAccount($platform, $cooldownMinutes = 30) {
|
||||
// Select an active account that was last active more than X minutes ago,
|
||||
// or has never been active (last_active is NULL).
|
||||
// Order by last_active ascending to rotate through them (least recently used first).
|
||||
|
||||
$stmt = $this->con->prepare("
|
||||
SELECT id, username, total_posts, total_comments
|
||||
FROM social_accounts
|
||||
WHERE platform = ?
|
||||
AND status = 'active'
|
||||
AND (last_active IS NULL OR last_active <= DATE_SUB(NOW(), INTERVAL ? MINUTE))
|
||||
ORDER BY last_active ASC, id ASC
|
||||
LIMIT 1
|
||||
");
|
||||
|
||||
$stmt->execute([$platform, $cooldownMinutes]);
|
||||
return $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the last active timestamp for an account
|
||||
*/
|
||||
public function markAccountActive($accountId) {
|
||||
$stmt = $this->con->prepare("UPDATE social_accounts SET last_active = NOW() WHERE id = ?");
|
||||
$stmt->execute([$accountId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment comment count
|
||||
*/
|
||||
public function incrementCommentCount($accountId) {
|
||||
$stmt = $this->con->prepare("UPDATE social_accounts SET total_comments = total_comments + 1, last_active = NOW() WHERE id = ?");
|
||||
$stmt->execute([$accountId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark an account as restricted or banned
|
||||
*/
|
||||
public function markAccountStatus($accountId, $status) {
|
||||
if (!in_array($status, ['active', 'restricted', 'banned'])) return false;
|
||||
|
||||
$stmt = $this->con->prepare("UPDATE social_accounts SET status = ? WHERE id = ?");
|
||||
return $stmt->execute([$status, $accountId]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// gemini_comment_generator.php
|
||||
// Connects to Gemini API to generate natural, driver-like comments
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../../core/bootstrap.php';
|
||||
require_once __DIR__ . '/../../functions.php';
|
||||
|
||||
// The API key should be stored in .env or the database. For now, reading from env.
|
||||
$geminiApiKey = getenv('GEMINI_API_KEY');
|
||||
|
||||
if (!$geminiApiKey) {
|
||||
die("Error: GEMINI_API_KEY not set in environment.\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a comment using Gemini API
|
||||
*
|
||||
* @param string $postContext The content of the post or the discussion
|
||||
* @param string $intent What the bot should try to achieve (e.g. "promote_siro", "agree_and_empathize")
|
||||
* @return string The generated comment
|
||||
*/
|
||||
function generateCommentWithGemini($postContext, $intent) {
|
||||
global $geminiApiKey;
|
||||
|
||||
$url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=" . $geminiApiKey;
|
||||
|
||||
$systemInstruction = "أنت سائق سيارة أجرة في الأردن أو السعودية. تتحدث بلهجة عامية بسيطة جداً، وتكتب أحياناً بأسلوب عفوي (ممكن مع أخطاء مطبعية بسيطة لتكون طبيعياً). أنت تستخدم تطبيق 'سيرو' (Siro) للنقل الذكي وتراه التطبيق الأفضل لأنه يوفر دخل ممتاز ولا يظلم السائق. لا تتحدث كمسوق لشركة، بل كزميل ناصح.";
|
||||
|
||||
$prompt = "السياق/المنشور الذي تقرأه الآن: \n\"" . $postContext . "\"\n\n";
|
||||
|
||||
if ($intent === 'promote_siro') {
|
||||
$prompt .= "المطلوب: اكتب تعليقاً تتعاطف فيه مع المشكلة المذكورة (إن وجدت)، وانصح الزملاء بتجربة تطبيق 'سيرو' كحل واقعي وممتاز لكباتن النقل الذكي. اجعل التعليق قصيراً ومباشراً.";
|
||||
} elseif ($intent === 'answer_question') {
|
||||
$prompt .= "المطلوب: أجب على السؤال المطروح في المنشور بوضوح بناءً على خبرتك مع تطبيق 'سيرو'.";
|
||||
} else {
|
||||
$prompt .= "المطلوب: اكتب تعليقاً متفاعلاً بشكل عام مع المنشور بأسلوب السائقين.";
|
||||
}
|
||||
|
||||
$data = [
|
||||
"contents" => [
|
||||
[
|
||||
"parts" => [
|
||||
["text" => $systemInstruction . "\n\n" . $prompt]
|
||||
]
|
||||
]
|
||||
],
|
||||
"generationConfig" => [
|
||||
"temperature" => 0.7, // A bit of creativity for varied responses
|
||||
"maxOutputTokens" => 150
|
||||
]
|
||||
];
|
||||
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode === 200) {
|
||||
$result = json_decode($response, true);
|
||||
if (isset($result['candidates'][0]['content']['parts'][0]['text'])) {
|
||||
return trim($result['candidates'][0]['content']['parts'][0]['text']);
|
||||
}
|
||||
}
|
||||
|
||||
return "والله يا صاحبي جربت تطبيق سيرو وارتحت كثير من المشاكل هذي، جربه ما بتندم."; // Fallback response
|
||||
}
|
||||
|
||||
// Example usage if called directly via CLI:
|
||||
if (php_sapi_name() === 'cli') {
|
||||
echo "Testing Gemini Generation...\n";
|
||||
$testContext = "التطبيقات الثانية بتاخذ عمولة عالية جداً ومش ملحقين بنزين!";
|
||||
$generated = generateCommentWithGemini($testContext, 'promote_siro');
|
||||
echo "Generated Comment: \n" . $generated . "\n";
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// schedule_manager.php
|
||||
// Creates and schedules tasks for the bots
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../../core/bootstrap.php';
|
||||
require_once __DIR__ . '/gemini_comment_generator.php';
|
||||
|
||||
class ScheduleManager {
|
||||
private $con;
|
||||
|
||||
public function __construct() {
|
||||
$this->con = Database::get('main');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new task and optionally generate a comment for it using Gemini.
|
||||
*
|
||||
* @param string $platform 'facebook' or 'instagram'
|
||||
* @param string $type 'join_group', 'read_posts', 'post_comment', 'share_link'
|
||||
* @param string|null $targetUrl The group or post URL
|
||||
* @param string|null $promptContext Context for Gemini
|
||||
* @param int|null $accountId Specific account ID or null for any available
|
||||
* @param int $delayMinutes How many minutes to delay this task
|
||||
*/
|
||||
public function scheduleTask($platform, $type, $targetUrl = null, $promptContext = null, $accountId = null, $delayMinutes = 0) {
|
||||
// Quiet hours check (e.g. don't schedule tasks between 1 AM and 6 AM)
|
||||
$currentHour = (int)date('H');
|
||||
if ($currentHour >= 1 && $currentHour < 6) {
|
||||
// Push it to 6 AM if we are in quiet hours
|
||||
$hoursToAdd = 6 - $currentHour;
|
||||
$delayMinutes += ($hoursToAdd * 60);
|
||||
}
|
||||
|
||||
$scheduledAt = date('Y-m-d H:i:s', strtotime("+$delayMinutes minutes"));
|
||||
|
||||
$generatedComment = null;
|
||||
if (($type === 'post_comment' || $type === 'share_link') && $promptContext) {
|
||||
// Pre-generate the comment using Gemini
|
||||
// If it's share_link, we want an intent to promote, otherwise just answer/interact
|
||||
$intent = ($type === 'share_link') ? 'promote_siro' : 'answer_question';
|
||||
$generatedComment = generateCommentWithGemini($promptContext, $intent);
|
||||
}
|
||||
|
||||
$stmt = $this->con->prepare("
|
||||
INSERT INTO social_tasks (account_id, platform, type, target_url, prompt_context, generated_comment, scheduled_at, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, 'pending')
|
||||
");
|
||||
|
||||
return $stmt->execute([
|
||||
$accountId,
|
||||
$platform,
|
||||
$type,
|
||||
$targetUrl,
|
||||
$promptContext,
|
||||
$generatedComment,
|
||||
$scheduledAt
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Example usage via CLI
|
||||
if (php_sapi_name() === 'cli' && isset($argv[1]) && $argv[1] === 'test_schedule') {
|
||||
$sm = new ScheduleManager();
|
||||
$context = "شو رأيكم بتطبيق سيرو يا كباتن؟ حد جربه؟";
|
||||
$success = $sm->scheduleTask('facebook', 'post_comment', 'https://facebook.com/groups/amman.drivers/post/12345', $context, null, 5);
|
||||
echo $success ? "Task scheduled successfully!\n" : "Failed to schedule task.\n";
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
-- Social Media Bot Schema
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `social_accounts` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`platform` ENUM('facebook', 'instagram') NOT NULL,
|
||||
`username` VARCHAR(100) NOT NULL,
|
||||
`status` ENUM('active', 'restricted', 'banned') DEFAULT 'active',
|
||||
`total_posts` INT DEFAULT 0,
|
||||
`total_comments` INT DEFAULT 0,
|
||||
`last_active` DATETIME NULL,
|
||||
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `social_tasks` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`account_id` INT NULL, -- NULL if any available account can take it
|
||||
`platform` ENUM('facebook', 'instagram') NOT NULL,
|
||||
`type` ENUM('join_group', 'read_posts', 'post_comment', 'share_link') NOT NULL,
|
||||
`target_url` VARCHAR(500) NULL, -- URL of the group or post
|
||||
`prompt_context` TEXT NULL, -- Context for Gemini to generate the comment
|
||||
`generated_comment` TEXT NULL, -- The comment generated by Gemini
|
||||
`status` ENUM('pending', 'in_progress', 'completed', 'failed') DEFAULT 'pending',
|
||||
`error_message` TEXT NULL,
|
||||
`scheduled_at` DATETIME NULL,
|
||||
`completed_at` DATETIME NULL,
|
||||
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`account_id`) REFERENCES `social_accounts`(`id`) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `social_logs` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`task_id` INT NULL,
|
||||
`account_id` INT NULL,
|
||||
`log_level` ENUM('info', 'warning', 'error') DEFAULT 'info',
|
||||
`message` TEXT NOT NULL,
|
||||
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`task_id`) REFERENCES `social_tasks`(`id`) ON DELETE SET NULL,
|
||||
FOREIGN KEY (`account_id`) REFERENCES `social_accounts`(`id`) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
-- Insert dummy account for testing
|
||||
INSERT IGNORE INTO `social_accounts` (`platform`, `username`, `status`) VALUES ('facebook', 'test_bot_1', 'active');
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// social_worker.php
|
||||
// Endpoint for the Android Social Media Bot
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../../core/bootstrap.php';
|
||||
require_once __DIR__ . '/../../functions.php';
|
||||
|
||||
// Authentication and validation could be added here similar to driver_socket.php
|
||||
// For now, let's keep it simple or use a static token for the bot
|
||||
$headers = getallheaders();
|
||||
$botToken = $headers['X-Bot-Token'] ?? '';
|
||||
if ($botToken !== 'YOUR_SECRET_BOT_TOKEN') {
|
||||
// In production, use a secure token or JWT
|
||||
// http_response_code(401);
|
||||
// exit(json_encode(['status' => 'error', 'message' => 'Unauthorized']));
|
||||
}
|
||||
|
||||
$action = $_GET['action'] ?? '';
|
||||
|
||||
try {
|
||||
$con = Database::get('main');
|
||||
|
||||
switch ($action) {
|
||||
case 'get_task':
|
||||
// The bot asks for a task to do
|
||||
$platform = $_GET['platform'] ?? 'facebook';
|
||||
|
||||
// Find a pending task that is scheduled for now or earlier
|
||||
$stmt = $con->prepare("
|
||||
SELECT id, type, target_url, prompt_context, generated_comment
|
||||
FROM social_tasks
|
||||
WHERE status = 'pending' AND platform = ? AND (scheduled_at IS NULL OR scheduled_at <= NOW())
|
||||
ORDER BY created_at ASC LIMIT 1
|
||||
");
|
||||
$stmt->execute([$platform]);
|
||||
$task = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($task) {
|
||||
// Mark as in progress
|
||||
$update = $con->prepare("UPDATE social_tasks SET status = 'in_progress' WHERE id = ?");
|
||||
$update->execute([$task['id']]);
|
||||
|
||||
echo json_encode(['status' => 'success', 'data' => $task]);
|
||||
} else {
|
||||
echo json_encode(['status' => 'success', 'message' => 'No tasks available', 'data' => null]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'complete_task':
|
||||
// The bot reports task completion
|
||||
$taskId = $_POST['task_id'] ?? null;
|
||||
$result = $_POST['result'] ?? ''; // e.g. success or error details
|
||||
|
||||
if ($taskId) {
|
||||
$stmt = $con->prepare("UPDATE social_tasks SET status = 'completed', completed_at = NOW() WHERE id = ?");
|
||||
$stmt->execute([$taskId]);
|
||||
|
||||
// Log it
|
||||
$log = $con->prepare("INSERT INTO social_logs (task_id, log_level, message) VALUES (?, 'info', ?)");
|
||||
$log->execute([$taskId, "Task completed: " . substr($result, 0, 200)]);
|
||||
|
||||
echo json_encode(['status' => 'success']);
|
||||
} else {
|
||||
echo json_encode(['status' => 'error', 'message' => 'task_id required']);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'fail_task':
|
||||
// The bot reports task failure
|
||||
$taskId = $_POST['task_id'] ?? null;
|
||||
$errorMsg = $_POST['error_message'] ?? 'Unknown error';
|
||||
|
||||
if ($taskId) {
|
||||
$stmt = $con->prepare("UPDATE social_tasks SET status = 'failed', error_message = ? WHERE id = ?");
|
||||
$stmt->execute([$errorMsg, $taskId]);
|
||||
|
||||
// Log it
|
||||
$log = $con->prepare("INSERT INTO social_logs (task_id, log_level, message) VALUES (?, 'error', ?)");
|
||||
$log->execute([$taskId, "Task failed: " . substr($errorMsg, 0, 200)]);
|
||||
|
||||
echo json_encode(['status' => 'success']);
|
||||
} else {
|
||||
echo json_encode(['status' => 'error', 'message' => 'task_id required']);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'log':
|
||||
// The bot sends a general log
|
||||
$accountId = $_POST['account_id'] ?? null;
|
||||
$level = $_POST['level'] ?? 'info';
|
||||
$message = $_POST['message'] ?? '';
|
||||
|
||||
$log = $con->prepare("INSERT INTO social_logs (account_id, log_level, message) VALUES (?, ?, ?)");
|
||||
$log->execute([$accountId, $level, $message]);
|
||||
|
||||
echo json_encode(['status' => 'success']);
|
||||
break;
|
||||
|
||||
default:
|
||||
echo json_encode(['status' => 'error', 'message' => 'Invalid action']);
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
echo json_encode(['status' => 'error', 'message' => 'Database error: ' . $e->getMessage()]);
|
||||
}
|
||||
+103
-107
@@ -29,10 +29,10 @@ try {
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 2. طلب الـ IDs والمواقع من سيرفر اللوكيشن (Redis API)
|
||||
// 2. طلب بيانات السائقين من سيرفر اللوكيشن (Redis API)
|
||||
// — يرجع cache hits لو موجودة، وإلا يرجع IDs فقط
|
||||
// ==========================================
|
||||
$locationServerUrl = getenv('LOCATION_API_URL');
|
||||
// تأكد من المسار الصحيح للمفتاح على السيرفر الرئيسي
|
||||
$INTERNAL_KEY = trim(file_get_contents(getenv('INTERNAL_SOCKET_KEY_PATH')));
|
||||
|
||||
$ch = curl_init();
|
||||
@@ -41,7 +41,7 @@ try {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
|
||||
'lat' => $lat,
|
||||
'lng' => $lng,
|
||||
'radius' => 5, // 5 كم كافية للعرض
|
||||
'radius' => 5,
|
||||
'limit' => 50
|
||||
]));
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
@@ -65,119 +65,115 @@ try {
|
||||
exit;
|
||||
}
|
||||
|
||||
// تجهيز خريطة لدمج البيانات لاحقاً (ID => RedisData)
|
||||
$driversMap = [];
|
||||
$driverIds = [];
|
||||
// تقسيم: cache hits → نستخدمها فوراً، cache misses → نحتاج MySQL
|
||||
$final_result = [];
|
||||
$mysqlIds = [];
|
||||
$driversMap = [];
|
||||
$serverNow = date('Y-m-d H:i:s');
|
||||
|
||||
foreach ($redisDrivers as $d) {
|
||||
$driverIds[] = $d['id'];
|
||||
$driversMap[$d['id']] = $d;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 3. جلب التفاصيل الكاملة من MySQL (مثل الملف القديم تماماً)
|
||||
// ==========================================
|
||||
|
||||
// تجهيز الـ Placeholders
|
||||
$placeholders = implode(',', array_fill(0, count($driverIds), '?'));
|
||||
|
||||
// الاستعلام الشامل (نفس الحقول القديمة)
|
||||
$sql_drivers_info = "
|
||||
SELECT
|
||||
d.id AS driver_id,
|
||||
d.phone, d.email, d.birthdate, d.first_name, d.last_name, d.gender, d.maritalStatus,
|
||||
cr.make,
|
||||
cr.car_plate,
|
||||
cr.model,
|
||||
cr.color, cr.vin, cr.color_hex,
|
||||
cr.year,
|
||||
cr.vehicle_category_id,
|
||||
dt.token,
|
||||
COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver
|
||||
FROM driver d
|
||||
LEFT JOIN CarRegistration cr ON cr.driverID = d.id
|
||||
LEFT JOIN driverToken dt ON dt.captain_id = d.id
|
||||
LEFT JOIN (
|
||||
SELECT driver_id, AVG(rating) AS ratingDriver
|
||||
FROM ratingDriver
|
||||
GROUP BY driver_id
|
||||
) rdAvg ON rdAvg.driver_id = d.id
|
||||
WHERE d.id IN ($placeholders)
|
||||
AND COALESCE(cr.year, 0) > 2000
|
||||
-- AND (cr.make NOT LIKE '%دراج%' AND cr.model NOT LIKE '%دراج%')
|
||||
";
|
||||
|
||||
$stmt = $con->prepare($sql_drivers_info);
|
||||
$stmt->execute($driverIds);
|
||||
$drivers_db = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if (empty($drivers_db)) {
|
||||
jsonSuccess([], "No matching drivers in DB");
|
||||
exit;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 4. معالجة البيانات، الدمج، وفك التشفير
|
||||
// ==========================================
|
||||
|
||||
$final_result = [];
|
||||
$serverNow = date('Y-m-d H:i:s');
|
||||
$fieldsToDecrypt = ['phone','email','gender','birthdate','first_name','last_name','token','car_plate','vin'];
|
||||
|
||||
// الهاش الخاص بالإناث (لتحديد النوع لاحقاً إذا لزم الأمر)
|
||||
// $femaleHash = 'bQ6yWJ2EVXKZooHdGclvmFiDlZCM8UYeO+ILFjDUvpQ=';
|
||||
|
||||
foreach ($drivers_db as $row) {
|
||||
$did = $row['driver_id'];
|
||||
|
||||
// دمج بيانات الموقع الحية من الريدز (أهم خطوة)
|
||||
if (isset($driversMap[$did])) {
|
||||
$redisInfo = $driversMap[$did];
|
||||
$row['latitude'] = $redisInfo['lat'];
|
||||
$row['longitude'] = $redisInfo['lng'];
|
||||
$row['heading'] = $redisInfo['heading'];
|
||||
$row['speed'] = $redisInfo['speed'];
|
||||
// $row['distance'] = $redisInfo['distance']; // إذا أردت إضافتها
|
||||
if (!empty($d['cached']) && !empty($d['first_name'])) {
|
||||
// ✅ Cache hit — بيانات السائق كاملة من Redis, نستخدمها مباشرة
|
||||
$d['serverNow'] = $serverNow;
|
||||
$final_result[] = $d;
|
||||
} else {
|
||||
// حالة نادرة: السائق موجود في الاستعلام ولكن ليس في مصفوفة الريدز (لا يجب أن تحدث)
|
||||
continue;
|
||||
// ❌ Cache miss — نحتاج نجيب البيانات من MySQL
|
||||
$mysqlIds[] = $d['id'];
|
||||
$driversMap[$d['id']] = $d;
|
||||
}
|
||||
}
|
||||
|
||||
$row['serverNow'] = $serverNow;
|
||||
|
||||
// فك التشفير (Decrypt)
|
||||
foreach ($fieldsToDecrypt as $field) {
|
||||
if (isset($row[$field]) && $row[$field] !== null && $row[$field] !== '') {
|
||||
try {
|
||||
$row[$field] = $encryptionHelper->decryptData($row[$field]);
|
||||
} catch (Exception $e) {
|
||||
$row[$field] = null;
|
||||
// ==========================================
|
||||
// 3. فقط للسائقين اللي ما فيهم Cache: MySQL
|
||||
// ==========================================
|
||||
if (!empty($mysqlIds)) {
|
||||
$placeholders = implode(',', array_fill(0, count($mysqlIds), '?'));
|
||||
|
||||
$sql_drivers_info = "
|
||||
SELECT
|
||||
d.id AS driver_id,
|
||||
d.phone, d.email, d.birthdate, d.first_name, d.last_name, d.gender, d.maritalStatus,
|
||||
cr.make, cr.car_plate, cr.model, cr.color, cr.vin, cr.color_hex, cr.year, cr.vehicle_category_id,
|
||||
dt.token,
|
||||
COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver
|
||||
FROM driver d
|
||||
LEFT JOIN CarRegistration cr ON cr.driverID = d.id
|
||||
LEFT JOIN driverToken dt ON dt.captain_id = d.id
|
||||
LEFT JOIN (
|
||||
SELECT driver_id, AVG(rating) AS ratingDriver FROM ratingDriver GROUP BY driver_id
|
||||
) rdAvg ON rdAvg.driver_id = d.id
|
||||
WHERE d.id IN ($placeholders)
|
||||
AND COALESCE(cr.year, 0) > 2000
|
||||
";
|
||||
|
||||
$stmt = $con->prepare($sql_drivers_info);
|
||||
$stmt->execute($mysqlIds);
|
||||
$drivers_db = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$fieldsToDecrypt = ['phone','email','gender','birthdate','first_name','last_name','token','car_plate','vin'];
|
||||
|
||||
foreach ($drivers_db as $row) {
|
||||
$did = $row['driver_id'];
|
||||
|
||||
if (isset($driversMap[$did])) {
|
||||
$redisInfo = $driversMap[$did];
|
||||
$row['latitude'] = $redisInfo['latitude'] ?? $redisInfo['lat'] ?? '';
|
||||
$row['longitude'] = $redisInfo['longitude'] ?? $redisInfo['lng'] ?? '';
|
||||
$row['heading'] = $redisInfo['heading'] ?? '0';
|
||||
$row['speed'] = $redisInfo['speed'] ?? '0';
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
$row['serverNow'] = $serverNow;
|
||||
|
||||
foreach ($fieldsToDecrypt as $field) {
|
||||
if (isset($row[$field]) && $row[$field] !== null && $row[$field] !== '') {
|
||||
try {
|
||||
$row[$field] = $encryptionHelper->decryptData($row[$field]);
|
||||
} catch (Exception $e) {
|
||||
$row[$field] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($row['birthdate'])) {
|
||||
try {
|
||||
$birthdate = new DateTime($row['birthdate']);
|
||||
$today = new DateTime();
|
||||
$row['age'] = $today->diff($birthdate)->y;
|
||||
} catch (Exception $e) { $row['age'] = null; }
|
||||
} else {
|
||||
$row['age'] = null;
|
||||
}
|
||||
|
||||
$final_result[] = $row;
|
||||
|
||||
// 🆕 Fill driver:public cache for next time (async, fire-and-forget)
|
||||
sendToLocationServer('cache_driver_public', [
|
||||
'driver_id' => $did,
|
||||
'data' => json_encode([
|
||||
'first_name' => $row['first_name'] ?? '',
|
||||
'last_name' => $row['last_name'] ?? '',
|
||||
'gender' => $row['gender'] ?? '',
|
||||
'make' => $row['make'] ?? '',
|
||||
'model' => $row['model'] ?? '',
|
||||
'color' => $row['color'] ?? '',
|
||||
'color_hex' => $row['color_hex'] ?? '',
|
||||
'year' => $row['year'] ?? '',
|
||||
'car_plate' => $row['car_plate'] ?? '',
|
||||
'ratingDriver'=> $row['ratingDriver'] ?? '0',
|
||||
'latitude' => $row['latitude'] ?? '',
|
||||
'longitude' => $row['longitude'] ?? '',
|
||||
'heading' => $row['heading'] ?? '0',
|
||||
'speed' => $row['speed'] ?? '0',
|
||||
'updated_at' => time()
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
// حساب العمر
|
||||
if (!empty($row['birthdate'])) {
|
||||
try {
|
||||
$birthdate = new DateTime($row['birthdate']);
|
||||
$today = new DateTime();
|
||||
$row['age'] = $today->diff($birthdate)->y;
|
||||
} catch (Exception $e) { $row['age'] = null; }
|
||||
} else {
|
||||
$row['age'] = null;
|
||||
}
|
||||
|
||||
// إضافة نوع السيارة البسيط (اختياري، إذا كان التطبيق يعتمد عليه)
|
||||
/*
|
||||
$type = 'car';
|
||||
if ($row['vehicle_category_id'] == 2) $type = 'bike';
|
||||
elseif ($row['gender'] == 'female') $type = 'lady'; // بعد فك التشفير تكون female
|
||||
$row['type'] = $type;
|
||||
*/
|
||||
|
||||
$final_result[] = $row;
|
||||
}
|
||||
|
||||
// إرجاع النتيجة بنفس الهيكل القديم
|
||||
// إرجاع النتيجة
|
||||
jsonSuccess($final_result);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
|
||||
@@ -199,13 +199,21 @@ try {
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// STEP F — تنظيف السوق (أبلغ location server إن الرحلة محجوزة)
|
||||
// STEP F — تنظيف السوق + Cache ride state (أبلغ location server)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
sendToLocationServer('ride_taken_event', [
|
||||
'ride_id' => $rideId,
|
||||
'taken_by_driver_id' => $driverId,
|
||||
]);
|
||||
|
||||
// 🆕 Cache ride state in Redis
|
||||
sendToLocationServer('update_ride_state', [
|
||||
'ride_id' => $rideId,
|
||||
'status' => $status,
|
||||
'driver_id' => $driverId,
|
||||
'passenger_id' => $passengerIdValue ?? '',
|
||||
]);
|
||||
|
||||
error_log("[accept_ride] SUCCESS. RideID=$rideId accepted by DriverID=$driverId");
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
@@ -67,6 +67,14 @@ try {
|
||||
}
|
||||
}
|
||||
|
||||
// 🆕 Cache ride state in Redis
|
||||
sendToLocationServer('update_ride_state', [
|
||||
'ride_id' => $rideId,
|
||||
'status' => 'arrived',
|
||||
'driver_id' => $driverId,
|
||||
'passenger_id' => $passenger_id ?? '',
|
||||
]);
|
||||
|
||||
jsonSuccess(null, "Arrival notified successfully");
|
||||
|
||||
} catch (Exception $e) {
|
||||
|
||||
@@ -212,6 +212,14 @@ try {
|
||||
|
||||
$con->commit();
|
||||
|
||||
// 🆕 Cache ride state in Redis
|
||||
sendToLocationServer('update_ride_state', [
|
||||
'ride_id' => $rideId,
|
||||
'status' => 'cancelled_by_driver',
|
||||
'driver_id' => $driverId,
|
||||
'passenger_id' => $passenger_id ?? '',
|
||||
]);
|
||||
|
||||
// 5. الرد للفلاتر
|
||||
echo json_encode([
|
||||
"status" => "success",
|
||||
|
||||
@@ -146,6 +146,14 @@ try {
|
||||
}
|
||||
}
|
||||
|
||||
// 🆕 Cache ride state in Redis
|
||||
sendToLocationServer('update_ride_state', [
|
||||
'ride_id' => $rideId,
|
||||
'status' => 'cancelled_by_passenger',
|
||||
'driver_id' => $driverId ?? '',
|
||||
'passenger_id' => $ride['passenger_id'] ?? '',
|
||||
]);
|
||||
|
||||
jsonSuccess(null, "Ride cancelled successfully");
|
||||
|
||||
} catch (PDOException $e) {
|
||||
|
||||
@@ -117,6 +117,15 @@ try {
|
||||
}
|
||||
|
||||
$con->commit();
|
||||
|
||||
// 🆕 Cache ride state in Redis
|
||||
sendToLocationServer('update_ride_state', [
|
||||
'ride_id' => $ride_id,
|
||||
'status' => 'started',
|
||||
'driver_id' => $driver_id,
|
||||
'passenger_id' => $passenger_id ?? '',
|
||||
]);
|
||||
|
||||
jsonSuccess(null, "Ride started successfully");
|
||||
|
||||
} catch (PDOException $e) {
|
||||
|
||||
Reference in New Issue
Block a user