getMessage() . "\n"); } // getRedisConnection() لا ترمي استثناءً — تُعيد null عند تعذّر الاتصال. // مفتاح مكافحة التكرار يعيش في Redis؛ بدونه يفشل الفحص "مفتوحاً" // فيتلقّى كل راكب نفس رسالة الاستعادة في كل تشغيل. if (!$redis) { error_log('[ReEngagement] ABORT: Redis unavailable — anti-spam guard would fail open.'); die("[ReEngagement] Redis unavailable — aborting to avoid duplicate pushes.\n"); } echo "[ReEngagement] Starting Passenger Re-engagement Engine...\n"; $REDIS_TTL = 72 * 3600; // anti-spam: 72 ساعة $INACTIVE_DAYS = 3; // عدد أيام الخمول $BATCH_LIMIT = 500; // أقصى عدد ركاب لكل تشغيل // ───────────────────────────────────────────────────────────── // 1. جلب الركاب الخاملين (آخر نشاطهم منذ 3 أيام أو أكثر) // نعتمد على passenger_opening_locations كمصدر لآخر نشاط // ───────────────────────────────────────────────────────────── $sql = " SELECT p.id AS passenger_id, IFNULL(MAX(pol.country_code), 'JO') AS country_code, t.token, MAX(pol.created_at) AS last_active FROM passengers p JOIN tokens t ON t.passengerID = p.id LEFT JOIN passenger_opening_locations pol ON pol.passenger_id = p.id WHERE p.status = 'notDeleted' GROUP BY p.id, t.token HAVING last_active IS NULL OR last_active < DATE_SUB(NOW(), INTERVAL :days DAY) ORDER BY last_active ASC LIMIT :limit "; $stmt = $con->prepare($sql); $stmt->bindValue(':days', $INACTIVE_DAYS, PDO::PARAM_INT); $stmt->bindValue(':limit', $BATCH_LIMIT, PDO::PARAM_INT); $stmt->execute(); $inactivePassengers = $stmt->fetchAll(PDO::FETCH_ASSOC); if (empty($inactivePassengers)) { echo "[ReEngagement] No inactive passengers found. All good!\n"; exit; } echo "[ReEngagement] Found " . count($inactivePassengers) . " inactive passengers (≥{$INACTIVE_DAYS} days).\n"; // ───────────────────────────────────────────────────────────── // 2. تجميع حسب الدولة لتقليل استدعاءات Gemini // ───────────────────────────────────────────────────────────── $byCountry = []; foreach ($inactivePassengers as $p) { $cc = strtoupper($p['country_code'] ?? 'SY'); $byCountry[$cc][] = $p; } $geminiService = new SiroGeminiService(); $encryptionHelper = new EncryptionHelper(); $sentCount = 0; $skippedCount = 0; foreach ($byCountry as $countryCode => $passengers) { echo "[ReEngagement] Country: $countryCode — " . count($passengers) . " passengers.\n"; // ─── رسالة Gemini مخصصة لدولة واحدة ─────────────────── $regionName = match($countryCode) { 'JO' => 'Amman', 'SY' => 'Damascus', 'EG' => 'Cairo', 'IQ' => 'Baghdad', default => 'your city' }; // سحب أسعار المنافسين كمرجع للرسالة $stmtComp = $con->prepare( "SELECT competitor_name, base_fare FROM competitor_secret_formulas WHERE country_code = :cc ORDER BY last_updated DESC LIMIT 3" ); $stmtComp->execute([':cc' => $countryCode]); $competitors = $stmtComp->fetchAll(PDO::FETCH_ASSOC); if (empty($competitors)) { $competitors = [['competitor_name' => 'Market Average', 'base_fare' => 1.0]]; } $aiCampaign = $geminiService->analyzeMarketAndDraftCampaign( $competitors, 1.0, $regionName, $countryCode ); // Fallback إذا Gemini ما أعطى فرصة if (!$aiCampaign || ($aiCampaign['opportunity_detected'] ?? false) !== true) { $countryEmoji = match($countryCode) { 'JO' => '🇯🇴', 'SY' => '🇸🇾', 'EG' => '🇪🇬', default => '🌍' }; $title = "وينك؟ نفتقدك! {$countryEmoji}"; $body = "مش شايفينك من {$INACTIVE_DAYS} أيام. سيارتك جاهزة وبأرخص سعر — اطلب الآن! 🚗"; } else { $title = $aiCampaign['push_title'] ?? '🚗 سيرو يفتقدك!'; $body = $aiCampaign['push_body'] ?? "لم تطلب منذ {$INACTIVE_DAYS} أيام — العروض لا تنتظر!"; } echo "[ReEngagement] Message: [{$title}] {$body}\n"; // ─── إرسال لكل راكب مع anti-spam ─────────────────────── foreach ($passengers as $p) { $passengerId = $p['passenger_id']; $redisKey = "reengagement:sent:{$passengerId}"; // Anti-spam: تخطّى إذا أرسلنا له خلال 72 ساعة if ($redis && $redis->exists($redisKey)) { $skippedCount++; continue; } $decryptedToken = $encryptionHelper->decryptData($p['token'] ?? ''); if (!$decryptedToken) continue; $result = sendFCM_Internal( $decryptedToken, $title, $body, [ 'type' => 're_engagement', 'days_inactive' => $INACTIVE_DAYS, 'screen' => 'home', ], 'Marketing', false, 'ding' ); // حفظ في Redis لمنع التكرار if ($redis) { $redis->setex($redisKey, $REDIS_TTL, '1'); } // حفظ في جدول notifications الداخلي (يظهر في صفحة الإشعارات) try { $ins = $con->prepare( "INSERT INTO notifications (title, body, passenger_id) VALUES (:title, :body, :pid)" ); $ins->execute([':title' => $title, ':body' => $body, ':pid' => $passengerId]); } catch (Exception $e) { error_log("[ReEngagement] DB insert error: " . $e->getMessage()); } $sentCount++; } } echo "[ReEngagement] Done ✅ — Sent: {$sentCount}, Skipped (anti-spam): {$skippedCount}\n"; ?>