Update: 2026-06-30 02:33:51

This commit is contained in:
Hamza-Ayed
2026-06-30 02:33:51 +03:00
parent c8546bb744
commit 152d4df0fc
8 changed files with 45 additions and 125 deletions

View File

@@ -42,11 +42,6 @@ class ScraperAccessibilityService : AccessibilityService() {
private val taxiFCollectedPrices = mutableListOf<Pair<Double, String>>() private val taxiFCollectedPrices = mutableListOf<Pair<Double, String>>()
private var taxiFPriceScrollAttempts = 0 private var taxiFPriceScrollAttempts = 0
// Multi-trip batch fields
private val taxiFMultiTripTrips = mutableListOf<JSONObject>()
private var taxiFMultiTripIndex = 0
private var taxiFPriceSubmitTime = 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",
@@ -90,25 +85,11 @@ class ScraperAccessibilityService : AccessibilityService() {
currentTask = task currentTask = task
currentAppName = task.optString("app") currentAppName = task.optString("app")
val taskId = task.optString("task_id") val taskId = task.optString("task_id")
val taskType = task.optString("type", "price_check")
Log.i(TAG, "Received Task: $taskId for App: $currentAppName, Type: $taskType") Log.i(TAG, "Received Task: $taskId for App: $currentAppName")
taxiFPickupDone = false taxiFPickupDone = false
taxiFDestinationDone = false taxiFDestinationDone = false
taxiFMultiTripTrips.clear()
taxiFMultiTripIndex = 0
taxiFPriceSubmitTime = 0L
if (taskType == "batch_multi_trip") {
val tripsJson = task.optJSONArray("trips")
if (tripsJson != null) {
for (i in 0 until tripsJson.length()) {
taxiFMultiTripTrips.add(tripsJson.getJSONObject(i))
}
}
Log.i(TAG, "TaxiF: Batch multi-trip loaded with ${taxiFMultiTripTrips.size} trips")
}
currentState = BotState.LAUNCHING_APP currentState = BotState.LAUNCHING_APP
// Launch the App // Launch the App
@@ -384,26 +365,17 @@ class ScraperAccessibilityService : AccessibilityService() {
val task = currentTask ?: return val task = currentTask ?: return
val taskId = task.optString("task_id") val taskId = task.optString("task_id")
val startLat: Double // Extract nested payload object where coordinates reside
val startLng: Double val payload = task.optJSONObject("payload")
val endLat: Double val startLat = payload?.optDouble("start_lat", 0.0) ?: 0.0
val endLng: Double val startLng = payload?.optDouble("start_lng", 0.0) ?: 0.0
val endLat = payload?.optDouble("end_lat", 0.0) ?: 0.0
val tripCoords = getCurrentTripLatLng() val endLng = payload?.optDouble("end_lng", 0.0) ?: 0.0
if (tripCoords != null) {
startLat = tripCoords.first[0]
startLng = tripCoords.first[1]
endLat = tripCoords.second[0]
endLng = tripCoords.second[1]
} else {
val payload = task.optJSONObject("payload")
startLat = payload?.optDouble("start_lat", 0.0) ?: 0.0
startLng = payload?.optDouble("start_lng", 0.0) ?: 0.0
endLat = payload?.optDouble("end_lat", 0.0) ?: 0.0
endLng = payload?.optDouble("end_lng", 0.0) ?: 0.0
}
// Calculate distance
val distanceKm = calculateDistanceInKm(startLat, startLng, endLat, endLng) val distanceKm = calculateDistanceInKm(startLat, startLng, endLat, endLng)
// Extract numeric digits from price
val numericPrice = rawPrice.replace(Regex("[^0-9.]"), "").toDoubleOrNull() ?: 0.0 val numericPrice = rawPrice.replace(Regex("[^0-9.]"), "").toDoubleOrNull() ?: 0.0
serviceScope.launch { serviceScope.launch {
@@ -424,6 +396,10 @@ class ScraperAccessibilityService : AccessibilityService() {
} else { } else {
Log.e(TAG, "Failed to submit price.") Log.e(TAG, "Failed to submit price.")
} }
// Go back to IDLE
currentState = BotState.IDLE
currentTask = null
} }
} }
@@ -510,10 +486,7 @@ class ScraperAccessibilityService : AccessibilityService() {
private fun handleTaxiFAutomation(rootNode: android.view.accessibility.AccessibilityNodeInfo) { private fun handleTaxiFAutomation(rootNode: android.view.accessibility.AccessibilityNodeInfo) {
val task = currentTask ?: return val task = currentTask ?: return
// 3-second cool-down between multi-trips Log.d(TAG, "TaxiF Automation event. State: $currentState")
if ((System.currentTimeMillis() - taxiFPriceSubmitTime) < 3000) return
Log.d(TAG, "TaxiF Automation event. State: $currentState" +
if (taxiFMultiTripTrips.isNotEmpty()) " Trip ${taxiFMultiTripIndex + 1}/${taxiFMultiTripTrips.size}" else "")
when (currentState) { when (currentState) {
BotState.NAVIGATING_HOME -> { BotState.NAVIGATING_HOME -> {
if (hasJustClickedHome()) return if (hasJustClickedHome()) return
@@ -539,7 +512,7 @@ class ScraperAccessibilityService : AccessibilityService() {
BotState.SEARCHING_START -> { BotState.SEARCHING_START -> {
if (detectTaxiFPriceScreen(rootNode)) { if (detectTaxiFPriceScreen(rootNode)) {
if (hasJustClickedLocation() || hasJustClickedSuggestion()) return if (hasJustClickedLocation() || hasJustClickedSuggestion()) return
if (!taxiFPickupDone && shouldEditLocation(rootNode, isPickup = true)) { if (!taxiFPickupDone && shouldEditLocation(rootNode, task, isPickup = true)) {
clickLocationRow(rootNode, isPickup = true) clickLocationRow(rootNode, isPickup = true)
taxiFLocationClickTime = System.currentTimeMillis() taxiFLocationClickTime = System.currentTimeMillis()
return return
@@ -548,7 +521,7 @@ class ScraperAccessibilityService : AccessibilityService() {
currentState = BotState.SEARCHING_END currentState = BotState.SEARCHING_END
return return
} }
if (!taxiFPickupDone && handleTaxiFSearchScreen(rootNode, getCurrentStartLocation())) { if (!taxiFPickupDone && handleTaxiFSearchScreen(rootNode, task.optString("start_location", "Amman"))) {
taxiFPickupDone = true taxiFPickupDone = true
Log.i(TAG, "TaxiF: Handled pickup search, waiting for price screen") Log.i(TAG, "TaxiF: Handled pickup search, waiting for price screen")
} }
@@ -556,7 +529,7 @@ class ScraperAccessibilityService : AccessibilityService() {
BotState.SEARCHING_END -> { BotState.SEARCHING_END -> {
if (detectTaxiFPriceScreen(rootNode)) { if (detectTaxiFPriceScreen(rootNode)) {
if (hasJustClickedLocation() || hasJustClickedSuggestion()) return if (hasJustClickedLocation() || hasJustClickedSuggestion()) return
if (!taxiFDestinationDone && shouldEditLocation(rootNode, isPickup = false)) { if (!taxiFDestinationDone && shouldEditLocation(rootNode, task, isPickup = false)) {
clickLocationRow(rootNode, isPickup = false) clickLocationRow(rootNode, isPickup = false)
taxiFLocationClickTime = System.currentTimeMillis() taxiFLocationClickTime = System.currentTimeMillis()
return return
@@ -567,7 +540,7 @@ class ScraperAccessibilityService : AccessibilityService() {
taxiFPriceScrollAttempts = 0 taxiFPriceScrollAttempts = 0
return return
} }
if (!taxiFDestinationDone && handleTaxiFSearchScreen(rootNode, getCurrentEndLocation())) { if (!taxiFDestinationDone && handleTaxiFSearchScreen(rootNode, task.optString("end_location", "Airport"))) {
taxiFDestinationDone = true taxiFDestinationDone = true
Log.i(TAG, "TaxiF: Handled dest search, waiting for price screen") Log.i(TAG, "TaxiF: Handled dest search, waiting for price screen")
} }
@@ -593,30 +566,6 @@ class ScraperAccessibilityService : AccessibilityService() {
return (System.currentTimeMillis() - taxiFSuggestionClickTime) < 1500 return (System.currentTimeMillis() - taxiFSuggestionClickTime) < 1500
} }
private fun getCurrentStartLocation(): String {
return if (taxiFMultiTripTrips.isNotEmpty() && taxiFMultiTripIndex < taxiFMultiTripTrips.size)
taxiFMultiTripTrips[taxiFMultiTripIndex].optString("start_location", "Amman")
else
currentTask?.optString("start_location", "Amman") ?: "Amman"
}
private fun getCurrentEndLocation(): String {
return if (taxiFMultiTripTrips.isNotEmpty() && taxiFMultiTripIndex < taxiFMultiTripTrips.size)
taxiFMultiTripTrips[taxiFMultiTripIndex].optString("end_location", "Airport")
else
currentTask?.optString("end_location", "Airport") ?: "Airport"
}
private fun getCurrentTripLatLng(): Pair<DoubleArray, DoubleArray>? {
return if (taxiFMultiTripTrips.isNotEmpty() && taxiFMultiTripIndex < taxiFMultiTripTrips.size) {
val trip = taxiFMultiTripTrips[taxiFMultiTripIndex]
Pair(
doubleArrayOf(trip.optDouble("start_lat", 0.0), trip.optDouble("start_lng", 0.0)),
doubleArrayOf(trip.optDouble("end_lat", 0.0), trip.optDouble("end_lng", 0.0))
)
} else null
}
private fun getLocationTexts(node: android.view.accessibility.AccessibilityNodeInfo?): List<String> { private fun getLocationTexts(node: android.view.accessibility.AccessibilityNodeInfo?): List<String> {
val texts = mutableListOf<String>() val texts = mutableListOf<String>()
collectLocationTexts(node, texts) collectLocationTexts(node, texts)
@@ -636,9 +585,9 @@ class ScraperAccessibilityService : AccessibilityService() {
} }
} }
private fun shouldEditLocation(rootNode: android.view.accessibility.AccessibilityNodeInfo, isPickup: Boolean): Boolean { private fun shouldEditLocation(rootNode: android.view.accessibility.AccessibilityNodeInfo, task: JSONObject, isPickup: Boolean): Boolean {
val locationTexts = getLocationTexts(rootNode) val locationTexts = getLocationTexts(rootNode)
val taskLoc = if (isPickup) getCurrentStartLocation() else getCurrentEndLocation() 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 -> val matchFound = locationTexts.any { text ->
text.contains(taskLoc, ignoreCase = true) || taskLoc.contains(text, ignoreCase = true) text.contains(taskLoc, ignoreCase = true) || taskLoc.contains(text, ignoreCase = true)
@@ -776,40 +725,13 @@ class ScraperAccessibilityService : AccessibilityService() {
Log.i(TAG, "TaxiF: Found price option: ${label.take(50)} = $price JOD") Log.i(TAG, "TaxiF: Found price option: ${label.take(50)} = $price JOD")
} }
val cheapest = prices.minByOrNull { it.first } ?: prices.first() val cheapest = prices.minByOrNull { it.first } ?: prices.first()
val tripLabel = if (taxiFMultiTripTrips.isNotEmpty()) Log.i(TAG, "TaxiF: Submitting cheapest price: ${cheapest.second.take(50)} = ${cheapest.first} JOD")
"Trip ${taxiFMultiTripIndex + 1}/${taxiFMultiTripTrips.size}: " else ""
Log.i(TAG, "TaxiF: ${tripLabel}Cheapest price: ${cheapest.first} JOD")
submitPriceToServer("${cheapest.first} JOD") submitPriceToServer("${cheapest.first} JOD")
advanceToNextTaxiFTrip()
} else { } else {
Log.w(TAG, "TaxiF: No JOD prices found${if (taxiFMultiTripTrips.isNotEmpty()) " for trip ${taxiFMultiTripIndex + 1}" else ""}") Log.w(TAG, "TaxiF: No JOD prices found, falling back to generic search")
if (taxiFMultiTripTrips.isEmpty()) { searchPriceByCurrency(rootNode)
searchPriceByCurrency(rootNode)
currentState = BotState.IDLE
}
}
}
private fun advanceToNextTaxiFTrip() {
taxiFPriceSubmitTime = System.currentTimeMillis()
if (taxiFMultiTripTrips.isNotEmpty()) {
taxiFMultiTripIndex++
taxiFPickupDone = false
taxiFDestinationDone = false
if (taxiFMultiTripIndex < taxiFMultiTripTrips.size) {
Log.i(TAG, "TaxiF: 3s cool-down then trip ${taxiFMultiTripIndex + 1}/${taxiFMultiTripTrips.size}")
currentState = BotState.SEARCHING_START
} else {
Log.i(TAG, "TaxiF: All ${taxiFMultiTripTrips.size} trips completed!")
currentState = BotState.IDLE
currentTask = null
taxiFMultiTripTrips.clear()
taxiFMultiTripIndex = 0
}
} else {
currentState = BotState.IDLE
} }
currentState = BotState.IDLE
} }
private fun findHorizontalRecyclerView(node: android.view.accessibility.AccessibilityNodeInfo?): android.view.accessibility.AccessibilityNodeInfo? { private fun findHorizontalRecyclerView(node: android.view.accessibility.AccessibilityNodeInfo?): android.view.accessibility.AccessibilityNodeInfo? {

View File

@@ -198,33 +198,31 @@ if (isset($_POST['action'])) {
[5, 11], // Tla Al-Ali → Hashmi (~5km) [5, 11], // Tla Al-Ali → Hashmi (~5km)
]; ];
$trips = []; $tasks = json_decode(file_get_contents(TASKS_FILE), true);
$count = 0;
foreach ($tripPairs as $i => $pair) { foreach ($tripPairs as $i => $pair) {
$start = $ammanLocations[$pair[0]]; $start = $ammanLocations[$pair[0]];
$end = $ammanLocations[$pair[1]]; $end = $ammanLocations[$pair[1]];
$trips[] = [ $taskId = "prc_" . uniqid();
'trip_index' => $i,
$newTask = [
'task_id' => $taskId,
'type' => 'price_check',
'app' => $app,
'start_location' => $start['name'], 'start_location' => $start['name'],
'end_location' => $end['name'], 'end_location' => $end['name'],
'start_lat' => $start['lat'], 'payload' => [
'start_lng' => $start['lng'], 'start_lat' => $start['lat'],
'end_lat' => $end['lat'], 'start_lng' => $start['lng'],
'end_lng' => $end['lng'], 'end_lat' => $end['lat'],
'end_lng' => $end['lng'],
],
]; ];
$tasks[] = $newTask;
$count++;
} }
$taskId = "batch_" . uniqid();
$batchTask = [
'task_id' => $taskId,
'type' => 'batch_multi_trip',
'app' => $app,
'trips' => $trips,
];
$tasks = json_decode(file_get_contents(TASKS_FILE), true);
$tasks[] = $batchTask;
file_put_contents(TASKS_FILE, json_encode($tasks, JSON_PRETTY_PRINT)); file_put_contents(TASKS_FILE, json_encode($tasks, JSON_PRETTY_PRINT));
$message = "Batch task with 10 trips generated! Task ID: $taskId"; $message = "Generated $count standard tasks (one per trip) for TaxiF!";
} elseif ($_POST['action'] === 'clear_tasks') { } elseif ($_POST['action'] === 'clear_tasks') {
file_put_contents(TASKS_FILE, json_encode([])); file_put_contents(TASKS_FILE, json_encode([]));
$message = "Task queue cleared successfully."; $message = "Task queue cleared successfully.";
@@ -712,10 +710,10 @@ $scrapedResults = json_decode(file_get_contents(RESULTS_FILE), true);
<form method="POST" action="" style="margin-top: 1.5rem; padding-top: 1.5rem; border-top: 1px solid var(--border-color);"> <form method="POST" action="" style="margin-top: 1.5rem; padding-top: 1.5rem; border-top: 1px solid var(--border-color);">
<input type="hidden" name="action" value="generate_10_trips"> <input type="hidden" name="action" value="generate_10_trips">
<input type="hidden" name="app" value="com.taxif.passenger"> <input type="hidden" name="app" value="com.taxif.passenger">
<h3 style="font-size:1rem; font-weight:600; margin-bottom:0.75rem; color:#fff;">Batch Multi-Trip Generator</h3> <h3 style="font-size:1rem; font-weight:600; margin-bottom:0.75rem; color:#fff;">Generate 10 Amman Trips</h3>
<p style="font-size:0.85rem; color:var(--text-muted); margin-bottom:1rem;"> <p style="font-size:0.85rem; color:var(--text-muted); margin-bottom:1rem;">
Generates 1 batch task containing 10 varied Amman trips (217 km) for TaxiF. Queues 10 separate standard tasks (one per trip) with varied distances (217 km) for TaxiF.
The bot processes all 10 trips without reopening the app. The bot processes each trip individually through normal polling.
</p> </p>
<button type="submit" class="btn" id="batch-btn" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%); box-shadow: 0 4px 15px rgba(16, 185, 129, 0.4);"> <button type="submit" class="btn" id="batch-btn" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%); box-shadow: 0 4px 15px rgba(16, 185, 129, 0.4);">
Generate 10 Amman Trips Generate 10 Amman Trips