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 taxiFPriceScrollAttempts = 0
|
||||||
private var taxiFReadingPriceStartTime = 0L
|
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 {
|
companion object {
|
||||||
private val TAXIF_EXCLUDED_TEXTS = setOf(
|
private val TAXIF_EXCLUDED_TEXTS = setOf(
|
||||||
"JOD", "ECO", "TaxiF", "SUV", "EV", "Mini", "Female", "Van",
|
"JOD", "ECO", "TaxiF", "SUV", "EV", "Mini", "Female", "Van",
|
||||||
@@ -872,6 +877,28 @@ class ScraperAccessibilityService : AccessibilityService() {
|
|||||||
return null
|
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) {
|
private fun handleJeenyAutomation(rootNode: android.view.accessibility.AccessibilityNodeInfo) {
|
||||||
val task = currentTask ?: return
|
val task = currentTask ?: return
|
||||||
Log.d(TAG, "Jeeny Automation event. State: $currentState")
|
Log.d(TAG, "Jeeny Automation event. State: $currentState")
|
||||||
@@ -879,7 +906,8 @@ class ScraperAccessibilityService : AccessibilityService() {
|
|||||||
when (currentState) {
|
when (currentState) {
|
||||||
BotState.SEARCHING_START -> {
|
BotState.SEARCHING_START -> {
|
||||||
// Look for the "Where to" button/view on the main screen
|
// 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, "Where to")
|
||||||
?: findNodeByText(rootNode, "إلى أين")
|
?: findNodeByText(rootNode, "إلى أين")
|
||||||
|
|
||||||
@@ -892,10 +920,18 @@ class ScraperAccessibilityService : AccessibilityService() {
|
|||||||
if (clickableParent != null) {
|
if (clickableParent != null) {
|
||||||
clickableParent.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK)
|
clickableParent.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK)
|
||||||
Log.i(TAG, "Jeeny: Clicked where to button/view.")
|
Log.i(TAG, "Jeeny: Clicked where to button/view.")
|
||||||
|
jeenyDestinationDone = false
|
||||||
|
jeenyPickupDone = false
|
||||||
|
jeenySearchTypingTime = 0L
|
||||||
|
jeenyPickupWaitStartTime = 0L
|
||||||
currentState = BotState.SEARCHING_END
|
currentState = BotState.SEARCHING_END
|
||||||
} else {
|
} else {
|
||||||
whereToNode.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK)
|
whereToNode.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK)
|
||||||
Log.i(TAG, "Jeeny: Clicked where to button/view (direct).")
|
Log.i(TAG, "Jeeny: Clicked where to button/view (direct).")
|
||||||
|
jeenyDestinationDone = false
|
||||||
|
jeenyPickupDone = false
|
||||||
|
jeenySearchTypingTime = 0L
|
||||||
|
jeenyPickupWaitStartTime = 0L
|
||||||
currentState = BotState.SEARCHING_END
|
currentState = BotState.SEARCHING_END
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -908,57 +944,77 @@ class ScraperAccessibilityService : AccessibilityService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
BotState.SEARCHING_END -> {
|
BotState.SEARCHING_END -> {
|
||||||
val endLoc = task.optString("end_location", "Airport")
|
if (!jeenyDestinationDone) {
|
||||||
val firstWord = endLoc.split(" ").firstOrNull() ?: ""
|
val endLoc = task.optString("end_location", "Airport")
|
||||||
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 (editTexts.size >= 2) {
|
val editTexts = findAllEditTexts(rootNode)
|
||||||
val pickupField = editTexts[0]
|
if (editTexts.isNotEmpty()) {
|
||||||
val destField = editTexts[1]
|
val destField = editTexts.last() // Destination is usually the last one
|
||||||
|
if (destField.text?.toString() != endLoc && !destField.text.toString().contains(endLoc)) {
|
||||||
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 destArgs = android.os.Bundle().apply {
|
val destArgs = android.os.Bundle().apply {
|
||||||
putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, endLoc)
|
putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, endLoc)
|
||||||
}
|
}
|
||||||
destField.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT, destArgs)
|
destField.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT, destArgs)
|
||||||
Log.i(TAG, "Jeeny: Set destination -> $endLoc")
|
Log.i(TAG, "Jeeny: Set destination -> $endLoc")
|
||||||
|
jeenySearchTypingTime = System.currentTimeMillis()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
val destField = editTexts[0]
|
|
||||||
if (destField.text?.toString() != endLoc) {
|
if (jeenySearchTypingTime > 0 && System.currentTimeMillis() - jeenySearchTypingTime > 1500) {
|
||||||
val destArgs = android.os.Bundle().apply {
|
val firstWord = endLoc.split(" ").firstOrNull() ?: endLoc
|
||||||
putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, 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)
|
pickupField.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT, pickupArgs)
|
||||||
Log.i(TAG, "Jeeny: Set single field -> $endLoc")
|
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
|
// 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 -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user