Update: 2026-06-30 02:33:51
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.
@@ -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? {
|
||||||
|
|||||||
@@ -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 (2–17 km) for TaxiF.
|
Queues 10 separate standard tasks (one per trip) with varied distances (2–17 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
|
||||||
|
|||||||
Reference in New Issue
Block a user