Update: 2026-06-30 04:14:29

This commit is contained in:
Hamza-Ayed
2026-06-30 04:14:29 +03:00
parent fdfe225b7b
commit 0d793fcce1
7 changed files with 57 additions and 35 deletions

View File

@@ -41,6 +41,7 @@ class ScraperAccessibilityService : AccessibilityService() {
private var taxiFDestinationDone = false private var taxiFDestinationDone = false
private val taxiFCollectedPrices = mutableListOf<Pair<Double, String>>() private val taxiFCollectedPrices = mutableListOf<Pair<Double, String>>()
private var taxiFPriceScrollAttempts = 0 private var taxiFPriceScrollAttempts = 0
private var taxiFReadingPriceStartTime = 0L
companion object { companion object {
private val TAXIF_EXCLUDED_TEXTS = setOf( private val TAXIF_EXCLUDED_TEXTS = setOf(
@@ -397,21 +398,19 @@ class ScraperAccessibilityService : AccessibilityService() {
Log.e(TAG, "Failed to submit price.") Log.e(TAG, "Failed to submit price.")
} }
// Wait 3 seconds, then fetch next trip directly (no relaunch) // Wait 3 seconds for price to settle, then fetch next task directly
delay(3000) delay(3000)
val nextResult = workerClient.fetchTask() val nextResult = workerClient.fetchTask()
if (nextResult != null && nextResult.optBoolean("has_task", false)) { if (nextResult != null && nextResult.optBoolean("has_task", false)) {
val nextTask = nextResult.getJSONObject("task") val nextTask = nextResult.getJSONObject("task")
val nextApp = nextTask.optString("app")
Log.i(TAG, "Multi-trip: Fetched next task for $nextApp, going directly to SEARCHING_START")
currentTask = nextTask currentTask = nextTask
currentAppName = nextApp
taxiFPickupDone = false taxiFPickupDone = false
taxiFDestinationDone = false taxiFDestinationDone = false
currentState = BotState.SEARCHING_START currentState = BotState.SEARCHING_START
Log.i(TAG, "TaxiF: Multi-trip fetched next task ${nextTask.optString("task_id")}, state -> SEARCHING_START")
} else { } else {
Log.d(TAG, "Multi-trip: No more tasks, going IDLE.") Log.i(TAG, "No more tasks, returning to IDLE.")
currentState = BotState.IDLE currentState = BotState.IDLE
currentTask = null currentTask = null
} }
@@ -551,6 +550,7 @@ class ScraperAccessibilityService : AccessibilityService() {
} }
taxiFDestinationDone = true taxiFDestinationDone = true
currentState = BotState.READING_PRICE currentState = BotState.READING_PRICE
taxiFReadingPriceStartTime = System.currentTimeMillis()
taxiFCollectedPrices.clear() taxiFCollectedPrices.clear()
taxiFPriceScrollAttempts = 0 taxiFPriceScrollAttempts = 0
return return
@@ -561,7 +561,16 @@ class ScraperAccessibilityService : AccessibilityService() {
} }
} }
BotState.READING_PRICE -> { BotState.READING_PRICE -> {
if (!hasJustClickedSuggestion()) { if (hasJustClickedSuggestion()) return
if (System.currentTimeMillis() - taxiFReadingPriceStartTime < 2000) return
val ecoPrice = getEcoPrice(rootNode)
if (ecoPrice != null && ecoPrice > 0) {
Log.i(TAG, "TaxiF: Found ECO price: $ecoPrice JOD")
submitPriceToServer("$ecoPrice JOD")
currentState = BotState.IDLE
} else if (System.currentTimeMillis() - taxiFReadingPriceStartTime > 15000) {
Log.w(TAG, "TaxiF: ECO price not found within 15s timeout, falling back to collectAndScrollPrices")
collectAndScrollPrices(rootNode) collectAndScrollPrices(rootNode)
} }
} }
@@ -570,44 +579,34 @@ class ScraperAccessibilityService : AccessibilityService() {
} }
private fun hasJustClickedHome(): Boolean { private fun hasJustClickedHome(): Boolean {
return (System.currentTimeMillis() - taxiFHomeClickTime) < 1500 return (System.currentTimeMillis() - taxiFHomeClickTime) < 500
} }
private fun hasJustClickedLocation(): Boolean { private fun hasJustClickedLocation(): Boolean {
return (System.currentTimeMillis() - taxiFLocationClickTime) < 1500 return (System.currentTimeMillis() - taxiFLocationClickTime) < 500
} }
private fun hasJustClickedSuggestion(): Boolean { private fun hasJustClickedSuggestion(): Boolean {
return (System.currentTimeMillis() - taxiFSuggestionClickTime) < 1500 return (System.currentTimeMillis() - taxiFSuggestionClickTime) < 500
}
private fun getLocationTexts(node: android.view.accessibility.AccessibilityNodeInfo?): List<String> {
val texts = mutableListOf<String>()
collectLocationTexts(node, texts)
return texts
}
private fun collectLocationTexts(node: android.view.accessibility.AccessibilityNodeInfo?, texts: MutableList<String>) {
if (node == null) return
val text = node.text?.toString()?.trim() ?: ""
val className = node.className?.toString() ?: ""
val isEditText = className == "android.widget.EditText" || node.isEditable
if (text.isNotBlank() && text.length > 2 && !isEditText && TAXIF_EXCLUDED_TEXTS.none { text.contains(it) }) {
texts.add(text)
}
for (i in 0 until node.childCount) {
collectLocationTexts(node.getChild(i), texts)
}
} }
private fun shouldEditLocation(rootNode: android.view.accessibility.AccessibilityNodeInfo, task: JSONObject, isPickup: Boolean): Boolean { private fun shouldEditLocation(rootNode: android.view.accessibility.AccessibilityNodeInfo, task: JSONObject, isPickup: Boolean): Boolean {
val locationTexts = getLocationTexts(rootNode) val locationNodes = getLocationTextNodes(rootNode)
val taskLoc = if (isPickup) task.optString("start_location", "") else task.optString("end_location", "") val taskLoc = if (isPickup) task.optString("start_location", "") else task.optString("end_location", "")
if (taskLoc.isEmpty()) return false if (taskLoc.isEmpty()) return false
val matchFound = locationTexts.any { text ->
text.contains(taskLoc, ignoreCase = true) || taskLoc.contains(text, ignoreCase = true) val rowNode = if (isPickup) {
locationNodes.firstOrNull()
} else {
val destIdx = locationNodes.indexOfFirst { it.text?.toString() == "عنوان الإنزال" }
if (destIdx >= 0) locationNodes[destIdx] else locationNodes.getOrNull(1)
} }
Log.d(TAG, "TaxiF: Location texts=$locationTexts, taskLoc=$taskLoc, matchFound=$matchFound")
val rowText = rowNode?.text?.toString()?.trim() ?: ""
val matchFound = rowText.contains(taskLoc, ignoreCase = true) || taskLoc.contains(rowText, ignoreCase = true)
Log.d(TAG, "TaxiF: shouldEditLocation(isPickup=$isPickup): rowText='$rowText', taskLoc='$taskLoc', matchFound=$matchFound, locationNodes=[${locationNodes.joinToString { it.text?.toString() ?: "" }}]")
return !matchFound return !matchFound
} }
@@ -656,7 +655,9 @@ class ScraperAccessibilityService : AccessibilityService() {
if (editTexts.isEmpty()) return false if (editTexts.isEmpty()) return false
val editField = editTexts[0] val editField = editTexts[0]
val currentText = editField.text?.toString() ?: "" val currentText = editField.text?.toString() ?: ""
if (currentText != location) { val normalizedCurrent = currentText.replace("-", " ").replace("\\s+".toRegex(), " ").trim()
val normalizedLocation = location.replace("-", " ").replace("\\s+".toRegex(), " ").trim()
if (!normalizedCurrent.contains(normalizedLocation, ignoreCase = true)) {
val arguments = android.os.Bundle().apply { val arguments = android.os.Bundle().apply {
putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, location) putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, location)
} }
@@ -689,7 +690,9 @@ class ScraperAccessibilityService : AccessibilityService() {
private fun suggestionContainsText(node: android.view.accessibility.AccessibilityNodeInfo?, target: String): Boolean { private fun suggestionContainsText(node: android.view.accessibility.AccessibilityNodeInfo?, target: String): Boolean {
if (node == null) return false if (node == null) return false
val text = node.text?.toString() ?: "" val text = node.text?.toString() ?: ""
if (text.contains(target, ignoreCase = true)) return true val normalizedText = text.replace("-", " ").replace("\\s+".toRegex(), " ").trim()
val normalizedTarget = target.replace("-", " ").replace("\\s+".toRegex(), " ").trim()
if (normalizedText.contains(normalizedTarget, ignoreCase = true)) return true
for (i in 0 until node.childCount) { for (i in 0 until node.childCount) {
if (suggestionContainsText(node.getChild(i), target)) return true if (suggestionContainsText(node.getChild(i), target)) return true
} }
@@ -780,6 +783,25 @@ class ScraperAccessibilityService : AccessibilityService() {
} }
} }
private fun getEcoPrice(rootNode: android.view.accessibility.AccessibilityNodeInfo?): Double? {
if (rootNode == null) return null
val recycler = findHorizontalRecyclerView(rootNode) ?: return null
for (i in 0 until recycler.childCount) {
val card = recycler.getChild(i) ?: continue
if (findNodeByText(card, "ECO") != null) {
val jodNode = findNodeByText(card, "JOD") ?: continue
val text = jodNode.text?.toString() ?: continue
val converted = arabicToWestern(text)
val match = Regex("""(\d+\.?\d*)""").find(converted)
if (match != null) {
val price = match.value.toDoubleOrNull()
if (price != null && price > 0) return price
}
}
}
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")