diff --git a/android_bot/.gradle/8.13/executionHistory/executionHistory.bin b/android_bot/.gradle/8.13/executionHistory/executionHistory.bin index 040e87c3..5a7b19ea 100644 Binary files a/android_bot/.gradle/8.13/executionHistory/executionHistory.bin and b/android_bot/.gradle/8.13/executionHistory/executionHistory.bin differ diff --git a/android_bot/.gradle/8.13/executionHistory/executionHistory.lock b/android_bot/.gradle/8.13/executionHistory/executionHistory.lock index 7d8f0465..9ce6ec44 100644 Binary files a/android_bot/.gradle/8.13/executionHistory/executionHistory.lock and b/android_bot/.gradle/8.13/executionHistory/executionHistory.lock differ diff --git a/android_bot/.gradle/8.13/fileHashes/fileHashes.bin b/android_bot/.gradle/8.13/fileHashes/fileHashes.bin index 7d8fe4aa..1f2641ab 100644 Binary files a/android_bot/.gradle/8.13/fileHashes/fileHashes.bin and b/android_bot/.gradle/8.13/fileHashes/fileHashes.bin differ diff --git a/android_bot/.gradle/8.13/fileHashes/fileHashes.lock b/android_bot/.gradle/8.13/fileHashes/fileHashes.lock index 8d4d4a31..2d6e042f 100644 Binary files a/android_bot/.gradle/8.13/fileHashes/fileHashes.lock and b/android_bot/.gradle/8.13/fileHashes/fileHashes.lock differ diff --git a/android_bot/.gradle/8.13/fileHashes/resourceHashesCache.bin b/android_bot/.gradle/8.13/fileHashes/resourceHashesCache.bin index 669b0552..f8949c41 100644 Binary files a/android_bot/.gradle/8.13/fileHashes/resourceHashesCache.bin and b/android_bot/.gradle/8.13/fileHashes/resourceHashesCache.bin differ diff --git a/android_bot/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/android_bot/.gradle/buildOutputCleanup/buildOutputCleanup.lock index 8c1fd746..302125bb 100644 Binary files a/android_bot/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/android_bot/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/android_bot/.gradle/buildOutputCleanup/outputFiles.bin b/android_bot/.gradle/buildOutputCleanup/outputFiles.bin index 2d8ab2c1..7bccaed8 100644 Binary files a/android_bot/.gradle/buildOutputCleanup/outputFiles.bin and b/android_bot/.gradle/buildOutputCleanup/outputFiles.bin differ diff --git a/android_bot/.gradle/file-system.probe b/android_bot/.gradle/file-system.probe index 31d1f58f..964ca61f 100644 Binary files a/android_bot/.gradle/file-system.probe and b/android_bot/.gradle/file-system.probe differ diff --git a/android_bot/app/src/main/java/com/siro/android_bot/service/AppLauncher.kt b/android_bot/app/src/main/java/com/siro/android_bot/service/AppLauncher.kt index 08c71f63..99d0faa2 100644 --- a/android_bot/app/src/main/java/com/siro/android_bot/service/AppLauncher.kt +++ b/android_bot/app/src/main/java/com/siro/android_bot/service/AppLauncher.kt @@ -15,7 +15,15 @@ object AppLauncher { "zaken" to "com.zakinn.app", "com.zakinn.app" to "com.zakinn.app", "tufaddal" to "com.bis.taxi", - "com.bis.taxi" to "com.bis.taxi" + "com.bis.taxi" to "com.bis.taxi", + "careem" to "com.careem.acma", + "com.careem.acma" to "com.careem.acma", + "uber" to "com.ubercab", + "com.ubercab" to "com.ubercab", + "taxif" to "com.taxif.passenger", + "com.taxif.passenger" to "com.taxif.passenger", + "jeeny" to "me.com.easytaxi", + "me.com.easytaxi" to "me.com.easytaxi" ) fun launchApp(context: Context, appName: String): Boolean { diff --git a/android_bot/app/src/main/java/com/siro/android_bot/service/ScraperAccessibilityService.kt b/android_bot/app/src/main/java/com/siro/android_bot/service/ScraperAccessibilityService.kt index 1d7ae8b2..5c20d3cd 100644 --- a/android_bot/app/src/main/java/com/siro/android_bot/service/ScraperAccessibilityService.kt +++ b/android_bot/app/src/main/java/com/siro/android_bot/service/ScraperAccessibilityService.kt @@ -98,6 +98,10 @@ class ScraperAccessibilityService : AccessibilityService() { "ae.com.yalla.go.dubai.client" -> handleYallaGoAutomation(rootNode) "com.zakinn.app" -> handleZakinnAutomation(rootNode) "com.bis.taxi" -> handleTfadalAutomation(rootNode) + "com.careem.acma" -> handleCareemAutomation(rootNode) + "com.ubercab" -> handleUberAutomation(rootNode) + "com.taxif.passenger" -> handleTaxiFAutomation(rootNode) + "me.com.easytaxi" -> handleJeenyAutomation(rootNode) } } @@ -322,7 +326,7 @@ class ScraperAccessibilityService : AccessibilityService() { private fun searchPriceByCurrency(node: android.view.accessibility.AccessibilityNodeInfo?) { if (node == null) return val text = node.text?.toString() ?: "" - if (text.contains("ل.س") || text.contains("SYP") || text.contains("AED") || text.contains("SP") || text.contains("SP.")) { + if (text.contains("ل.س") || text.contains("SYP") || text.contains("AED") || text.contains("SP") || text.contains("SP.") || text.contains("JOD") || text.contains("د.أ") || text.contains("JD")) { Log.i(TAG, "Found price pattern dynamically: $text") submitPriceToServer(text) return @@ -385,6 +389,146 @@ class ScraperAccessibilityService : AccessibilityService() { return r * c } + private fun handleCareemAutomation(rootNode: android.view.accessibility.AccessibilityNodeInfo) { + val task = currentTask ?: return + Log.d(TAG, "Careem Automation event. State: $currentState") + when (currentState) { + BotState.SEARCHING_START -> { + val pickupEdit = findEditableNode(rootNode) + if (pickupEdit != null) { + val startLoc = task.optString("start_location", "Amman") + val arguments = android.os.Bundle().apply { + putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, startLoc) + } + pickupEdit.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT, arguments) + Log.i(TAG, "Careem: Entered start: $startLoc") + currentState = BotState.SEARCHING_END + } + } + BotState.SEARCHING_END -> { + val destEdit = findEditableNode(rootNode) + if (destEdit != null) { + val endLoc = task.optString("end_location", "Airport") + val arguments = android.os.Bundle().apply { + putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, endLoc) + } + destEdit.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT, arguments) + Log.i(TAG, "Careem: Entered end: $endLoc") + currentState = BotState.READING_PRICE + } + } + BotState.READING_PRICE -> { + searchPriceByCurrency(rootNode) + } + else -> {} + } + } + + private fun handleUberAutomation(rootNode: android.view.accessibility.AccessibilityNodeInfo) { + val task = currentTask ?: return + Log.d(TAG, "Uber Automation event. State: $currentState") + when (currentState) { + BotState.SEARCHING_START -> { + val pickupEdit = findEditableNode(rootNode) + if (pickupEdit != null) { + val startLoc = task.optString("start_location", "Amman") + val arguments = android.os.Bundle().apply { + putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, startLoc) + } + pickupEdit.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT, arguments) + Log.i(TAG, "Uber: Entered start: $startLoc") + currentState = BotState.SEARCHING_END + } + } + BotState.SEARCHING_END -> { + val destEdit = findEditableNode(rootNode) + if (destEdit != null) { + val endLoc = task.optString("end_location", "Airport") + val arguments = android.os.Bundle().apply { + putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, endLoc) + } + destEdit.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT, arguments) + Log.i(TAG, "Uber: Entered end: $endLoc") + currentState = BotState.READING_PRICE + } + } + BotState.READING_PRICE -> { + searchPriceByCurrency(rootNode) + } + else -> {} + } + } + + private fun handleTaxiFAutomation(rootNode: android.view.accessibility.AccessibilityNodeInfo) { + val task = currentTask ?: return + Log.d(TAG, "TaxiF Automation event. State: $currentState") + when (currentState) { + BotState.SEARCHING_START -> { + val pickupEdit = findEditableNode(rootNode) + if (pickupEdit != null) { + val startLoc = task.optString("start_location", "Amman") + val arguments = android.os.Bundle().apply { + putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, startLoc) + } + pickupEdit.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT, arguments) + Log.i(TAG, "TaxiF: Entered start: $startLoc") + currentState = BotState.SEARCHING_END + } + } + BotState.SEARCHING_END -> { + val destEdit = findEditableNode(rootNode) + if (destEdit != null) { + val endLoc = task.optString("end_location", "Airport") + val arguments = android.os.Bundle().apply { + putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, endLoc) + } + destEdit.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT, arguments) + Log.i(TAG, "TaxiF: Entered end: $endLoc") + currentState = BotState.READING_PRICE + } + } + BotState.READING_PRICE -> { + searchPriceByCurrency(rootNode) + } + else -> {} + } + } + + private fun handleJeenyAutomation(rootNode: android.view.accessibility.AccessibilityNodeInfo) { + val task = currentTask ?: return + Log.d(TAG, "Jeeny Automation event. State: $currentState") + when (currentState) { + BotState.SEARCHING_START -> { + val pickupEdit = findEditableNode(rootNode) + if (pickupEdit != null) { + val startLoc = task.optString("start_location", "Amman") + val arguments = android.os.Bundle().apply { + putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, startLoc) + } + pickupEdit.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT, arguments) + Log.i(TAG, "Jeeny: Entered start: $startLoc") + currentState = BotState.SEARCHING_END + } + } + BotState.SEARCHING_END -> { + val destEdit = findEditableNode(rootNode) + if (destEdit != null) { + val endLoc = task.optString("end_location", "Airport") + val arguments = android.os.Bundle().apply { + putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, endLoc) + } + destEdit.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT, arguments) + Log.i(TAG, "Jeeny: Entered end: $endLoc") + currentState = BotState.READING_PRICE + } + } + BotState.READING_PRICE -> { + searchPriceByCurrency(rootNode) + } + else -> {} + } + } + override fun onInterrupt() { Log.w(TAG, "Accessibility Service Interrupted") } diff --git a/backend/bot/generate_price_tasks.php b/backend/bot/generate_price_tasks.php index dc079ac8..34423a28 100644 --- a/backend/bot/generate_price_tasks.php +++ b/backend/bot/generate_price_tasks.php @@ -34,22 +34,40 @@ CREATE TABLE IF NOT EXISTS competitor_prices ( "; $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], +// 2. Ten Key Regions in Damascus (Syria) and Amman (Jordan) +$countriesConfig = [ + 'SY' => [ + 'competitors' => ['yallago', 'zaken', 'tufaddal'], + '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], + ] + ], + 'JO' => [ + 'competitors' => ['com.careem.acma', 'com.ubercab', 'com.taxif.passenger', 'me.com.easytaxi'], + 'regions' => [ + ['name' => 'Abdoun', 'lat' => 31.9392, 'lng' => 35.8942], + ['name' => 'Jabal Amman', 'lat' => 31.9511, 'lng' => 35.9189], + ['name' => 'Sweileh', 'lat' => 32.0167, 'lng' => 35.8333], + ['name' => 'Khalda', 'lat' => 31.9861, 'lng' => 35.8450], + ['name' => 'Al-Jubaiha', 'lat' => 32.0194, 'lng' => 35.8753], + ['name' => 'Tla Al-Ali', 'lat' => 31.9961, 'lng' => 35.8647], + ['name' => 'Shmeisani', 'lat' => 31.9680, 'lng' => 35.9020], + ['name' => 'Um Uthaina', 'lat' => 31.9610, 'lng' => 35.8770], + ['name' => 'Jabal Al-Weibdeh', 'lat' => 31.9560, 'lng' => 35.9220], + ['name' => 'Marj Al-Hamam', 'lat' => 31.9000, 'lng' => 35.8500], + ] + ] ]; -$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 @@ -72,39 +90,44 @@ function generateRandomPoint($lat, $lng, $radius) { $tasksCreated = 0; -foreach ($regions as $region) { - // A. Generate Start Point (within 2km of region center) - $start = generateRandomPoint($region['lat'], $region['lng'], 2); +foreach ($countriesConfig as $countryCode => $config) { + $competitors = $config['competitors']; + $regions = $config['regions']; - // B. Generate Short Trip (2-5 km from start) - $shortDist = rand(20, 50) / 10.0; - $shortEnd = generateRandomPoint($start['lat'], $start['lng'], $shortDist); + foreach ($regions as $region) { + // A. Generate Start Point (within 2km of region center) + $start = generateRandomPoint($region['lat'], $region['lng'], 2); - // C. Generate Long Trip (10-15 km from start) - $longDist = rand(100, 150) / 10.0; - $longEnd = generateRandomPoint($start['lat'], $start['lng'], $longDist); + // B. Generate Short Trip (2-5 km from start) + $shortDist = rand(20, 50) / 10.0; + $shortEnd = generateRandomPoint($start['lat'], $start['lng'], $shortDist); - $trips = [$shortEnd, $longEnd]; + // C. Generate Long Trip (10-15 km from start) + $longDist = rand(100, 150) / 10.0; + $longEnd = generateRandomPoint($start['lat'], $start['lng'], $longDist); - 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'] - ] - ]; + $trips = [$shortEnd, $longEnd]; - // Push to Redis Queue - $redis->lpush('queue:bot:tasks', json_encode($taskData)); - $tasksCreated++; + 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++; + } } } } diff --git a/backend/bot/standalone_worker.php b/backend/bot/standalone_worker.php index fd03db7d..b772caec 100644 --- a/backend/bot/standalone_worker.php +++ b/backend/bot/standalone_worker.php @@ -506,6 +506,10 @@ $scrapedResults = json_decode(file_get_contents(RESULTS_FILE), true); .app-yallago { background: rgba(59, 130, 246, 0.15); color: #60a5fa; border: 1px solid rgba(59, 130, 246, 0.3); } .app-zaken { background: rgba(16, 185, 129, 0.15); color: #34d399; border: 1px solid rgba(16, 185, 129, 0.3); } .app-tufaddal { background: rgba(245, 158, 11, 0.15); color: #fbbf24; border: 1px solid rgba(245, 158, 11, 0.3); } + .app-careem { background: rgba(16, 185, 129, 0.15); color: #34d399; border: 1px solid rgba(16, 185, 129, 0.3); } + .app-uber { background: rgba(255, 255, 255, 0.1); color: #f3f4f6; border: 1px solid rgba(255, 255, 255, 0.2); } + .app-taxif { background: rgba(239, 68, 68, 0.15); color: #f87171; border: 1px solid rgba(239, 68, 68, 0.3); } + .app-jeeny { background: rgba(139, 92, 246, 0.15); color: #a78bfa; border: 1px solid rgba(139, 92, 246, 0.3); } .time-badge { font-size: 0.80rem; @@ -601,6 +605,10 @@ $scrapedResults = json_decode(file_get_contents(RESULTS_FILE), true); + + + + @@ -664,7 +672,24 @@ $scrapedResults = json_decode(file_get_contents(RESULTS_FILE), true);