Update: 2026-06-30 00:22:46
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.
@@ -5,6 +5,16 @@
|
|||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
|
<queries>
|
||||||
|
<package android:name="ae.com.yalla.go.dubai.client" />
|
||||||
|
<package android:name="com.zakinn.app" />
|
||||||
|
<package android:name="com.bis.taxi" />
|
||||||
|
<package android:name="com.careem.acma" />
|
||||||
|
<package android:name="com.ubercab" />
|
||||||
|
<package android:name="com.taxif.passenger" />
|
||||||
|
<package android:name="me.com.easytaxi" />
|
||||||
|
</queries>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class WorkerClient(private val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Change this to your actual server domain
|
// Change this to your actual server domain
|
||||||
private val BASE_URL = "https://api.intaleq.xyz/bot_android/standalone_worker.php"
|
private val BASE_URL = "https://jordan-siro.intaleqapp.com/backend/bot/standalone_worker.php"
|
||||||
// For local testing use: "http://10.0.2.2:8000/standalone_worker.php"
|
// For local testing use: "http://10.0.2.2:8000/standalone_worker.php"
|
||||||
|
|
||||||
private fun generateSignature(deviceId: String, ts: Long): String {
|
private fun generateSignature(deviceId: String, ts: Long): String {
|
||||||
|
|||||||
@@ -94,6 +94,14 @@ class ScraperAccessibilityService : AccessibilityService() {
|
|||||||
val packageName = event.packageName?.toString() ?: return
|
val packageName = event.packageName?.toString() ?: return
|
||||||
val rootNode = rootInActiveWindow ?: return
|
val rootNode = rootInActiveWindow ?: return
|
||||||
|
|
||||||
|
// Log layout hierarchy when a Jordanian app is open to inspect UI structure
|
||||||
|
if (packageName == "com.careem.acma" || packageName == "com.ubercab" ||
|
||||||
|
packageName == "com.taxif.passenger" || packageName == "me.com.easytaxi") {
|
||||||
|
Log.d("HierarchyDump", "--- START SCREEN HIERARCHY DUMP FOR $packageName ---")
|
||||||
|
dumpNodeHierarchy(rootNode, 0)
|
||||||
|
Log.d("HierarchyDump", "--- END SCREEN HIERARCHY DUMP FOR $packageName ---")
|
||||||
|
}
|
||||||
|
|
||||||
when (packageName) {
|
when (packageName) {
|
||||||
"ae.com.yalla.go.dubai.client" -> handleYallaGoAutomation(rootNode)
|
"ae.com.yalla.go.dubai.client" -> handleYallaGoAutomation(rootNode)
|
||||||
"com.zakinn.app" -> handleZakinnAutomation(rootNode)
|
"com.zakinn.app" -> handleZakinnAutomation(rootNode)
|
||||||
@@ -497,29 +505,92 @@ class ScraperAccessibilityService : AccessibilityService() {
|
|||||||
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")
|
||||||
|
|
||||||
when (currentState) {
|
when (currentState) {
|
||||||
BotState.SEARCHING_START -> {
|
BotState.SEARCHING_START -> {
|
||||||
val pickupEdit = findEditableNode(rootNode)
|
// Look for the "Where to" button/view on the main screen
|
||||||
if (pickupEdit != null) {
|
val whereToNode = findNodeByText(rootNode, "إلى اين تريد الذهاب")
|
||||||
val startLoc = task.optString("start_location", "Amman")
|
?: findNodeByText(rootNode, "Where to")
|
||||||
val arguments = android.os.Bundle().apply {
|
?: findNodeByText(rootNode, "إلى أين")
|
||||||
putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, startLoc)
|
|
||||||
|
if (whereToNode != null) {
|
||||||
|
// Click the parent clickable view if the TextView itself is not clickable
|
||||||
|
var clickableParent = whereToNode
|
||||||
|
while (clickableParent != null && !clickableParent.isClickable) {
|
||||||
|
clickableParent = clickableParent.parent
|
||||||
|
}
|
||||||
|
if (clickableParent != null) {
|
||||||
|
clickableParent.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK)
|
||||||
|
Log.i(TAG, "Jeeny: Clicked where to button/view.")
|
||||||
|
currentState = BotState.SEARCHING_END
|
||||||
|
} else {
|
||||||
|
whereToNode.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK)
|
||||||
|
Log.i(TAG, "Jeeny: Clicked where to button/view (direct).")
|
||||||
|
currentState = BotState.SEARCHING_END
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If we are already on the search screen (we don't see "Where to" main button, but we see EditTexts)
|
||||||
|
val editTexts = findAllEditTexts(rootNode)
|
||||||
|
if (editTexts.isNotEmpty()) {
|
||||||
|
Log.i(TAG, "Jeeny: Already on search screen with ${editTexts.size} input fields.")
|
||||||
|
currentState = BotState.SEARCHING_END
|
||||||
}
|
}
|
||||||
pickupEdit.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT, arguments)
|
|
||||||
Log.i(TAG, "Jeeny: Entered start: $startLoc")
|
|
||||||
currentState = BotState.SEARCHING_END
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BotState.SEARCHING_END -> {
|
BotState.SEARCHING_END -> {
|
||||||
val destEdit = findEditableNode(rootNode)
|
val endLoc = task.optString("end_location", "Airport")
|
||||||
if (destEdit != null) {
|
val firstWord = endLoc.split(" ").firstOrNull() ?: ""
|
||||||
val endLoc = task.optString("end_location", "Airport")
|
val resultNode = if (firstWord.isNotEmpty()) findNodeByText(rootNode, firstWord) else null
|
||||||
val arguments = android.os.Bundle().apply {
|
val isEditText = resultNode?.className == "android.widget.EditText" || resultNode?.isEditable == true
|
||||||
putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, endLoc)
|
|
||||||
|
// 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 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")
|
||||||
|
}
|
||||||
|
} 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)
|
||||||
|
}
|
||||||
|
destField.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT, destArgs)
|
||||||
|
Log.i(TAG, "Jeeny: Set single field -> $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 -> {
|
BotState.READING_PRICE -> {
|
||||||
@@ -529,6 +600,31 @@ class ScraperAccessibilityService : AccessibilityService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun dumpNodeHierarchy(node: android.view.accessibility.AccessibilityNodeInfo?, depth: Int) {
|
||||||
|
if (node == null) return
|
||||||
|
val indent = " ".repeat(depth)
|
||||||
|
val className = node.className ?: "unknown"
|
||||||
|
val text = node.text ?: ""
|
||||||
|
val resourceId = node.viewIdResourceName ?: "no-id"
|
||||||
|
val isClickable = node.isClickable
|
||||||
|
val isEditable = node.isEditable
|
||||||
|
Log.d("HierarchyDump", "$indent[$className] ID: $resourceId | Text: \"$text\" | Clickable: $isClickable | Editable: $isEditable")
|
||||||
|
for (i in 0 until node.childCount) {
|
||||||
|
dumpNodeHierarchy(node.getChild(i), depth + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findAllEditTexts(node: android.view.accessibility.AccessibilityNodeInfo?, list: MutableList<android.view.accessibility.AccessibilityNodeInfo> = mutableListOf()): List<android.view.accessibility.AccessibilityNodeInfo> {
|
||||||
|
if (node == null) return list
|
||||||
|
if (node.className == "android.widget.EditText" || node.isEditable) {
|
||||||
|
list.add(node)
|
||||||
|
}
|
||||||
|
for (i in 0 until node.childCount) {
|
||||||
|
findAllEditTexts(node.getChild(i), list)
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
override fun onInterrupt() {
|
override fun onInterrupt() {
|
||||||
Log.w(TAG, "Accessibility Service Interrupted")
|
Log.w(TAG, "Accessibility Service Interrupted")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user