Update: 2026-06-30 14:19:01
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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 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 (!jeenyDestinationDone) {
|
||||
val endLoc = task.optString("end_location", "Airport")
|
||||
|
||||
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 -> {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user