diff --git a/socialBot/app/src/main/java/com/siro/socialmedia_bot/social/facebook/FacebookBotService.kt b/socialBot/app/src/main/java/com/siro/socialmedia_bot/social/facebook/FacebookBotService.kt index 615c9deb..a8cbbf2b 100644 --- a/socialBot/app/src/main/java/com/siro/socialmedia_bot/social/facebook/FacebookBotService.kt +++ b/socialBot/app/src/main/java/com/siro/socialmedia_bot/social/facebook/FacebookBotService.kt @@ -102,9 +102,11 @@ class FacebookBotService : AccessibilityService() { val allCollectedTexts = mutableSetOf() val totalIterations = 20 // 20 iterations * ~15 sec = ~5 minutes of scrolling var iterations = 0 + var consecutiveNoNewPosts = 0 while (iterations < totalIterations) { Log.d(TAG, "Autonomous Iteration: ${iterations + 1} of $totalIterations") + val initialSize = allCollectedTexts.size // 1. Expand texts commentReader.expandPostText() @@ -136,6 +138,17 @@ class FacebookBotService : AccessibilityService() { allCollectedTexts.addAll(posts) Log.d(TAG, "Collected ${posts.size} texts in this iteration. Total unique so far: ${allCollectedTexts.size}") + if (allCollectedTexts.size == initialSize) { + consecutiveNoNewPosts++ + Log.d(TAG, "No new posts found in this iteration. (Streak: $consecutiveNoNewPosts)") + if (consecutiveNoNewPosts >= 3) { + Log.d(TAG, "No new posts for 3 consecutive iterations. Reached bottom or stuck. Breaking early.") + break + } + } else { + consecutiveNoNewPosts = 0 + } + Log.d(TAG, "Scrolling down for more posts...") navigator.scrollForward() delay(4000) // wait for new posts to load diff --git a/socialBot/app/src/main/java/com/siro/socialmedia_bot/social/facebook/FacebookNavigator.kt b/socialBot/app/src/main/java/com/siro/socialmedia_bot/social/facebook/FacebookNavigator.kt index 5f574ad7..f60ccc30 100644 --- a/socialBot/app/src/main/java/com/siro/socialmedia_bot/social/facebook/FacebookNavigator.kt +++ b/socialBot/app/src/main/java/com/siro/socialmedia_bot/social/facebook/FacebookNavigator.kt @@ -74,23 +74,24 @@ class FacebookNavigator(private val service: AccessibilityService) { } suspend fun scrollForward(): Boolean { - // Scroll 2 times using DOM to guarantee we pass large posts var success = false - val root = service.rootInActiveWindow ?: return false - - for (i in 0 until 2) { + + // DOM scroll attempts — refetch root each iteration to avoid stale nodes + for (i in 0 until 3) { + val root = service.rootInActiveWindow ?: continue if (performDomScroll(root)) { success = true } kotlinx.coroutines.delay(1000) } - + if (!success) { - android.util.Log.d("FacebookNavigator", "DOM Scroll failed or found nothing, using Gesture Fallback!") - // Do 2 gestures just to be sure we move past the post - for (i in 0 until 2) { + android.util.Log.d("FacebookNavigator", "DOM Scroll failed, using Gesture Fallback!") + kotlinx.coroutines.delay(800) + // More attempts with moderate delay to let Facebook process each scroll + for (i in 0 until 4) { success = performGestureScroll() - kotlinx.coroutines.delay(1000) + kotlinx.coroutines.delay(1200) } } return success @@ -99,17 +100,18 @@ class FacebookNavigator(private val service: AccessibilityService) { private suspend fun performGestureScroll(): Boolean = kotlinx.coroutines.suspendCancellableCoroutine { continuation -> val displayMetrics = service.resources.displayMetrics val middleX = displayMetrics.widthPixels / 2f - val startY = displayMetrics.heightPixels * 0.8f // Start near bottom - val endY = displayMetrics.heightPixels * 0.2f // Swipe up to near top - + // Moderate swipe to avoid accidental pull-to-refresh; longer duration for reliability + val startY = displayMetrics.heightPixels * 0.75f + val endY = displayMetrics.heightPixels * 0.35f + val path = android.graphics.Path() path.moveTo(middleX, startY) path.lineTo(middleX, endY) - + val gesture = android.accessibilityservice.GestureDescription.Builder() - .addStroke(android.accessibilityservice.GestureDescription.StrokeDescription(path, 0, 500)) + .addStroke(android.accessibilityservice.GestureDescription.StrokeDescription(path, 0, 1200)) .build() - + val dispatched = service.dispatchGesture(gesture, object : AccessibilityService.GestureResultCallback() { override fun onCompleted(gestureDescription: android.accessibilityservice.GestureDescription?) { if (continuation.isActive) continuation.resume(true, null) @@ -118,7 +120,7 @@ class FacebookNavigator(private val service: AccessibilityService) { if (continuation.isActive) continuation.resume(false, null) } }, null) - + if (!dispatched) { if (continuation.isActive) continuation.resume(false, null) } @@ -132,32 +134,62 @@ class FacebookNavigator(private val service: AccessibilityService) { private fun performDomScroll(node: android.view.accessibility.AccessibilityNodeInfo?): Boolean { if (node == null) return false - val className = node.className?.toString() ?: "" - - // CRITICAL FIX: ViewPager responds to SCROLL_FORWARD by swiping tabs horizontally! - // We MUST ignore ViewPagers to prevent the bot from changing tabs to the Menu. - val isHorizontal = className.contains("ViewPager", ignoreCase = true) || - className.contains("Horizontal", ignoreCase = true) + val candidates = mutableListOf() + val displayMetrics = service.resources.displayMetrics + val screenWidth = displayMetrics.widthPixels + val screenHeight = displayMetrics.heightPixels - if (!isHorizontal && node.isScrollable) { - val rect = android.graphics.Rect() - node.getBoundsInScreen(rect) - val displayMetrics = service.resources.displayMetrics - - // The main feed is a LARGE vertical container (height > width AND height > 40% of screen). - if (rect.height() > rect.width() && rect.height() > displayMetrics.heightPixels * 0.4f) { - val scrolled = node.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) - if (scrolled) { - return true + fun collectCandidates(n: android.view.accessibility.AccessibilityNodeInfo) { + val className = n.className?.toString() ?: "" + + val isHorizontal = className.contains("ViewPager", ignoreCase = true) || + className.contains("Horizontal", ignoreCase = true) + + if (!isHorizontal) { + val rect = android.graphics.Rect() + n.getBoundsInScreen(rect) + + // Main feed: vertical, tall, spans full screen width. + // isScrollable is deliberately NOT required — Facebook often sets it to false. + if (rect.height() > rect.width() && + rect.height() > screenHeight * 0.3f && + rect.width() >= screenWidth * 0.85f) { + candidates.add(n) } } - } - for (i in 0 until node.childCount) { - if (performDomScroll(node.getChild(i))) { - return true + for (i in 0 until n.childCount) { + n.getChild(i)?.let { collectCandidates(it) } } } + + collectCandidates(node) + + // Prefer RecyclerView/ListView, then by largest area + candidates.sortWith(Comparator { a, b -> + fun isFeedCandidate(c: android.view.accessibility.AccessibilityNodeInfo): Boolean { + val cn = c.className?.toString() ?: "" + return cn.contains("RecyclerView", ignoreCase = true) || + cn.contains("ListView", ignoreCase = true) + } + val aPref = isFeedCandidate(a) + val bPref = isFeedCandidate(b) + if (aPref != bPref) return@Comparator if (aPref) -1 else 1 + + val aRect = android.graphics.Rect().also { a.getBoundsInScreen(it) } + val bRect = android.graphics.Rect().also { b.getBoundsInScreen(it) } + (bRect.width() * bRect.height()).compareTo(aRect.width() * aRect.height()) + }) + + for (candidate in candidates) { + try { + val scrolled = candidate.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) + if (scrolled) return true + } catch (_: Exception) { + // performAction may throw on non-scrollable nodes + } + } + return false }