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 var taxiFPriceScrollAttempts = 0
// Multi-trip batch fields
private val taxiFMultiTripTrips = mutableListOf<JSONObject>()
private var taxiFMultiTripIndex = 0
private var taxiFPriceSubmitTime = 0L
companion object {
private val TAXIF_EXCLUDED_TEXTS = setOf(
"JOD", "ECO", "TaxiF", "SUV", "EV", "Mini", "Female", "Van",
@@ -90,25 +85,11 @@ class ScraperAccessibilityService : AccessibilityService() {
currentTask = task
currentAppName = task.optString("app")
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
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
// Launch the App
@@ -384,26 +365,17 @@ class ScraperAccessibilityService : AccessibilityService() {
val task = currentTask ?: return
val taskId = task.optString("task_id")
val startLat: Double
val startLng: Double
val endLat: Double
val endLng: Double
val tripCoords = getCurrentTripLatLng()
if (tripCoords != null) {
startLat = tripCoords.first[0]
startLng = tripCoords.first[1]
endLat = tripCoords.second[0]
endLng = tripCoords.second[1]
} else {
// Extract nested payload object where coordinates reside
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
}
val startLat = payload?.optDouble("start_lat", 0.0) ?: 0.0
val startLng = payload?.optDouble("start_lng", 0.0) ?: 0.0
val endLat = payload?.optDouble("end_lat", 0.0) ?: 0.0
val endLng = payload?.optDouble("end_lng", 0.0) ?: 0.0
// Calculate distance
val distanceKm = calculateDistanceInKm(startLat, startLng, endLat, endLng)
// Extract numeric digits from price
val numericPrice = rawPrice.replace(Regex("[^0-9.]"), "").toDoubleOrNull() ?: 0.0
serviceScope.launch {
@@ -424,6 +396,10 @@ class ScraperAccessibilityService : AccessibilityService() {
} else {
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) {
val task = currentTask ?: return
// 3-second cool-down between multi-trips
if ((System.currentTimeMillis() - taxiFPriceSubmitTime) < 3000) return
Log.d(TAG, "TaxiF Automation event. State: $currentState" +
if (taxiFMultiTripTrips.isNotEmpty()) " Trip ${taxiFMultiTripIndex + 1}/${taxiFMultiTripTrips.size}" else "")
Log.d(TAG, "TaxiF Automation event. State: $currentState")
when (currentState) {
BotState.NAVIGATING_HOME -> {
if (hasJustClickedHome()) return
@@ -539,7 +512,7 @@ class ScraperAccessibilityService : AccessibilityService() {
BotState.SEARCHING_START -> {
if (detectTaxiFPriceScreen(rootNode)) {
if (hasJustClickedLocation() || hasJustClickedSuggestion()) return
if (!taxiFPickupDone && shouldEditLocation(rootNode, isPickup = true)) {
if (!taxiFPickupDone && shouldEditLocation(rootNode, task, isPickup = true)) {
clickLocationRow(rootNode, isPickup = true)
taxiFLocationClickTime = System.currentTimeMillis()
return
@@ -548,7 +521,7 @@ class ScraperAccessibilityService : AccessibilityService() {
currentState = BotState.SEARCHING_END
return
}
if (!taxiFPickupDone && handleTaxiFSearchScreen(rootNode, getCurrentStartLocation())) {
if (!taxiFPickupDone && handleTaxiFSearchScreen(rootNode, task.optString("start_location", "Amman"))) {
taxiFPickupDone = true
Log.i(TAG, "TaxiF: Handled pickup search, waiting for price screen")
}
@@ -556,7 +529,7 @@ class ScraperAccessibilityService : AccessibilityService() {
BotState.SEARCHING_END -> {
if (detectTaxiFPriceScreen(rootNode)) {
if (hasJustClickedLocation() || hasJustClickedSuggestion()) return
if (!taxiFDestinationDone && shouldEditLocation(rootNode, isPickup = false)) {
if (!taxiFDestinationDone && shouldEditLocation(rootNode, task, isPickup = false)) {
clickLocationRow(rootNode, isPickup = false)
taxiFLocationClickTime = System.currentTimeMillis()
return
@@ -567,7 +540,7 @@ class ScraperAccessibilityService : AccessibilityService() {
taxiFPriceScrollAttempts = 0
return
}
if (!taxiFDestinationDone && handleTaxiFSearchScreen(rootNode, getCurrentEndLocation())) {
if (!taxiFDestinationDone && handleTaxiFSearchScreen(rootNode, task.optString("end_location", "Airport"))) {
taxiFDestinationDone = true
Log.i(TAG, "TaxiF: Handled dest search, waiting for price screen")
}
@@ -593,30 +566,6 @@ class ScraperAccessibilityService : AccessibilityService() {
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> {
val texts = mutableListOf<String>()
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 taskLoc = if (isPickup) getCurrentStartLocation() else getCurrentEndLocation()
val taskLoc = if (isPickup) task.optString("start_location", "") else task.optString("end_location", "")
if (taskLoc.isEmpty()) return false
val matchFound = locationTexts.any { text ->
text.contains(taskLoc, ignoreCase = true) || taskLoc.contains(text, ignoreCase = true)
@@ -776,41 +725,14 @@ class ScraperAccessibilityService : AccessibilityService() {
Log.i(TAG, "TaxiF: Found price option: ${label.take(50)} = $price JOD")
}
val cheapest = prices.minByOrNull { it.first } ?: prices.first()
val tripLabel = if (taxiFMultiTripTrips.isNotEmpty())
"Trip ${taxiFMultiTripIndex + 1}/${taxiFMultiTripTrips.size}: " else ""
Log.i(TAG, "TaxiF: ${tripLabel}Cheapest price: ${cheapest.first} JOD")
Log.i(TAG, "TaxiF: Submitting cheapest price: ${cheapest.second.take(50)} = ${cheapest.first} JOD")
submitPriceToServer("${cheapest.first} JOD")
advanceToNextTaxiFTrip()
} else {
Log.w(TAG, "TaxiF: No JOD prices found${if (taxiFMultiTripTrips.isNotEmpty()) " for trip ${taxiFMultiTripIndex + 1}" else ""}")
if (taxiFMultiTripTrips.isEmpty()) {
Log.w(TAG, "TaxiF: No JOD prices found, falling back to generic search")
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
}
}
private fun findHorizontalRecyclerView(node: android.view.accessibility.AccessibilityNodeInfo?): android.view.accessibility.AccessibilityNodeInfo? {
if (node == null) return null

View File

@@ -198,33 +198,31 @@ if (isset($_POST['action'])) {
[5, 11], // Tla Al-Ali → Hashmi (~5km)
];
$trips = [];
$tasks = json_decode(file_get_contents(TASKS_FILE), true);
$count = 0;
foreach ($tripPairs as $i => $pair) {
$start = $ammanLocations[$pair[0]];
$end = $ammanLocations[$pair[1]];
$trips[] = [
'trip_index' => $i,
$taskId = "prc_" . uniqid();
$newTask = [
'task_id' => $taskId,
'type' => 'price_check',
'app' => $app,
'start_location' => $start['name'],
'end_location' => $end['name'],
'payload' => [
'start_lat' => $start['lat'],
'start_lng' => $start['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));
$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') {
file_put_contents(TASKS_FILE, json_encode([]));
$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);">
<input type="hidden" name="action" value="generate_10_trips">
<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;">
Generates 1 batch task containing 10 varied Amman trips (217 km) for TaxiF.
The bot processes all 10 trips without reopening the app.
Queues 10 separate standard tasks (one per trip) with varied distances (217 km) for TaxiF.
The bot processes each trip individually through normal polling.
</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);">
Generate 10 Amman Trips