Update: 2026-06-30 14:19:01

This commit is contained in:
Hamza-Ayed
2026-06-30 14:19:01 +03:00
parent de1f5ced47
commit ef0ee91fd9
7 changed files with 109 additions and 45 deletions

View File

@@ -46,6 +46,11 @@ class ScraperAccessibilityService : AccessibilityService() {
private var taxiFPriceScrollAttempts = 0
private var taxiFReadingPriceStartTime = 0L
// Jeeny State Memory
private var jeenyDestinationDone = false
private var jeenyPickupDone = false
private var jeenySearchTypingTime = 0L
private var jeenyPickupWaitStartTime = 0L
companion object {
private val TAXIF_EXCLUDED_TEXTS = setOf(
"JOD", "ECO", "TaxiF", "SUV", "EV", "Mini", "Female", "Van",
@@ -872,6 +877,28 @@ class ScraperAccessibilityService : AccessibilityService() {
return null
}
private fun getJeenyEcoLitePrice(rootNode: android.view.accessibility.AccessibilityNodeInfo?): Double? {
if (rootNode == null) return null
val ecoNode = findNodeByText(rootNode, "Eco lite")
?: findNodeByText(rootNode, "EcoLite")
?: findNodeByText(rootNode, "ايكو لايت")
?: findNodeByText(rootNode, "إيكو لايت")
if (ecoNode != null) {
var parent = ecoNode.parent
for (i in 0..2) { // search up to 3 levels up
if (parent == null) break
val prices = mutableListOf<Pair<Double, String>>()
findAllJODPrices(parent, prices)
if (prices.isNotEmpty()) {
return prices.first().first
}
parent = parent.parent
}
}
return null
}
private fun handleJeenyAutomation(rootNode: android.view.accessibility.AccessibilityNodeInfo) {
val task = currentTask ?: return
Log.d(TAG, "Jeeny Automation event. State: $currentState")
@@ -879,7 +906,8 @@ class ScraperAccessibilityService : AccessibilityService() {
when (currentState) {
BotState.SEARCHING_START -> {
// Look for the "Where to" button/view on the main screen
val whereToNode = findNodeByText(rootNode, "إلى اين تريد الذهاب")
val whereToNode = findNodeByText(rootNode, "موقع الوصول")
?: findNodeByText(rootNode, "إلى اين تريد الذهاب")
?: findNodeByText(rootNode, "Where to")
?: findNodeByText(rootNode, "إلى أين")
@@ -892,10 +920,18 @@ class ScraperAccessibilityService : AccessibilityService() {
if (clickableParent != null) {
clickableParent.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK)
Log.i(TAG, "Jeeny: Clicked where to button/view.")
jeenyDestinationDone = false
jeenyPickupDone = false
jeenySearchTypingTime = 0L
jeenyPickupWaitStartTime = 0L
currentState = BotState.SEARCHING_END
} else {
whereToNode.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK)
Log.i(TAG, "Jeeny: Clicked where to button/view (direct).")
jeenyDestinationDone = false
jeenyPickupDone = false
jeenySearchTypingTime = 0L
jeenyPickupWaitStartTime = 0L
currentState = BotState.SEARCHING_END
}
} else {
@@ -908,57 +944,77 @@ class ScraperAccessibilityService : AccessibilityService() {
}
}
BotState.SEARCHING_END -> {
val endLoc = task.optString("end_location", "Airport")
val firstWord = endLoc.split(" ").firstOrNull() ?: ""
val resultNode = if (firstWord.isNotEmpty()) findNodeByText(rootNode, firstWord) else null
val isEditText = resultNode?.className == "android.widget.EditText" || resultNode?.isEditable == true
if (!jeenyDestinationDone) {
val endLoc = task.optString("end_location", "Airport")
// If a search result item containing the target location words is found (not the input field itself)
if (resultNode != null && !isEditText) {
var clickableParent = resultNode
while (clickableParent != null && !clickableParent.isClickable) {
clickableParent = clickableParent.parent
}
if (clickableParent != null) {
clickableParent.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK)
Log.i(TAG, "Jeeny: Clicked search result containing '$firstWord'.")
currentState = BotState.READING_PRICE
return
}
}
// Otherwise, enter the locations to populate the list
val editTexts = findAllEditTexts(rootNode)
if (editTexts.isNotEmpty()) {
val startLoc = task.optString("start_location", "Amman")
if (editTexts.size >= 2) {
val pickupField = editTexts[0]
val destField = editTexts[1]
if (pickupField.text?.toString() != startLoc) {
val pickupArgs = android.os.Bundle().apply {
putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, startLoc)
}
pickupField.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT, pickupArgs)
Log.i(TAG, "Jeeny: Set pickup -> $startLoc")
}
if (destField.text?.toString() != endLoc) {
val editTexts = findAllEditTexts(rootNode)
if (editTexts.isNotEmpty()) {
val destField = editTexts.last() // Destination is usually the last one
if (destField.text?.toString() != endLoc && !destField.text.toString().contains(endLoc)) {
val destArgs = android.os.Bundle().apply {
putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, endLoc)
}
destField.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT, destArgs)
Log.i(TAG, "Jeeny: Set destination -> $endLoc")
jeenySearchTypingTime = System.currentTimeMillis()
return
}
} else {
val destField = editTexts[0]
if (destField.text?.toString() != endLoc) {
val destArgs = android.os.Bundle().apply {
putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, endLoc)
}
if (jeenySearchTypingTime > 0 && System.currentTimeMillis() - jeenySearchTypingTime > 1500) {
val firstWord = endLoc.split(" ").firstOrNull() ?: endLoc
if (clickFirstSuggestion(rootNode, firstWord) || clickFirstGenericSuggestion(rootNode)) {
Log.i(TAG, "Jeeny: Clicked destination suggestion")
jeenyDestinationDone = true
jeenyPickupWaitStartTime = System.currentTimeMillis()
jeenySearchTypingTime = 0L
return
}
}
} else if (!jeenyPickupDone) {
// Wait 2 seconds for pickup widget to appear
if (jeenyPickupWaitStartTime > 0 && System.currentTimeMillis() - jeenyPickupWaitStartTime < 2000) {
return
}
val startLoc = task.optString("start_location", "Amman")
// The pickup search box might just be an EditText or we might need to click "موقع الانطلاق" first
val pickupTextNode = findNodeByText(rootNode, "موقع الانطلاق")
if (pickupTextNode != null) {
var clickableParent = pickupTextNode
while (clickableParent != null && !clickableParent.isClickable) {
clickableParent = clickableParent.parent
}
if (clickableParent != null) {
clickableParent.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK)
Log.i(TAG, "Jeeny: Clicked pickup location widget")
jeenyPickupWaitStartTime = 0L // prevent re-clicking immediately
return
}
}
val editTexts = findAllEditTexts(rootNode)
if (editTexts.isNotEmpty()) {
val pickupField = editTexts.first() // Pickup is usually the first one when both are visible
if (pickupField.text?.toString() != startLoc && !pickupField.text.toString().contains(startLoc)) {
val pickupArgs = android.os.Bundle().apply {
putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, startLoc)
}
destField.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT, destArgs)
Log.i(TAG, "Jeeny: Set single field -> $endLoc")
pickupField.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT, pickupArgs)
Log.i(TAG, "Jeeny: Set pickup -> $startLoc")
jeenySearchTypingTime = System.currentTimeMillis()
return
}
}
if (jeenySearchTypingTime > 0 && System.currentTimeMillis() - jeenySearchTypingTime > 1500) {
val firstWord = startLoc.split(" ").firstOrNull() ?: startLoc
if (clickFirstSuggestion(rootNode, firstWord) || clickFirstGenericSuggestion(rootNode)) {
Log.i(TAG, "Jeeny: Clicked pickup suggestion")
jeenyPickupDone = true
currentState = BotState.READING_PRICE
return
}
}
}
@@ -983,7 +1039,15 @@ class ScraperAccessibilityService : AccessibilityService() {
}
// If not on pickup confirmation screen, read the price
searchPriceByCurrency(rootNode)
val ecoLitePrice = getJeenyEcoLitePrice(rootNode)
if (ecoLitePrice != null && ecoLitePrice > 0) {
Log.i(TAG, "Jeeny: Found Eco lite price: $ecoLitePrice JOD")
submitPriceToServer("$ecoLitePrice JOD")
currentState = BotState.IDLE
} else {
// Fallback to searching any price if eco lite isn't strictly matched
searchPriceByCurrency(rootNode)
}
}
else -> {}
}