Update: 2026-07-04 21:38:44
This commit is contained in:
@@ -54,6 +54,43 @@ object SocialBotClient {
|
||||
postData("action=complete_task", "task_id=$taskId&result=$result")
|
||||
}
|
||||
|
||||
fun evaluatePosts(posts: List<String>): JSONObject? {
|
||||
try {
|
||||
val urlString = "$BASE_URL?action=evaluate_posts"
|
||||
val url = URL(urlString)
|
||||
val connection = url.openConnection() as HttpURLConnection
|
||||
connection.requestMethod = "POST"
|
||||
connection.setRequestProperty("X-Bot-Token", BOT_TOKEN)
|
||||
connection.setRequestProperty("Content-Type", "application/json")
|
||||
connection.doOutput = true
|
||||
|
||||
val jsonBody = JSONObject()
|
||||
val jsonArray = org.json.JSONArray(posts)
|
||||
jsonBody.put("posts", jsonArray)
|
||||
|
||||
val writer = OutputStreamWriter(connection.outputStream)
|
||||
writer.write(jsonBody.toString())
|
||||
writer.flush()
|
||||
writer.close()
|
||||
|
||||
val responseCode = connection.responseCode
|
||||
if (responseCode == 200) {
|
||||
val scanner = Scanner(connection.inputStream)
|
||||
val response = if (scanner.hasNext()) scanner.useDelimiter("\\A").next() else ""
|
||||
scanner.close()
|
||||
|
||||
val json = JSONObject(response)
|
||||
if (json.getString("status") == "success" && !json.isNull("data")) {
|
||||
return json.getJSONObject("data")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("SocialBotClient", "Exception in evaluatePosts: ${e.message}")
|
||||
e.printStackTrace()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun failTask(taskId: Int, errorMessage: String) {
|
||||
postData("action=fail_task", "task_id=$taskId&error_message=$errorMessage")
|
||||
}
|
||||
|
||||
+23
@@ -94,6 +94,29 @@ class FacebookBotService : AccessibilityService() {
|
||||
}
|
||||
}
|
||||
}
|
||||
"autonomous_scroll_and_reply" -> {
|
||||
Log.d(TAG, "Starting autonomous mode. Opening Facebook app...")
|
||||
navigator.openApp()
|
||||
delay(3000)
|
||||
|
||||
var iterations = 0
|
||||
while (iterations < 5) {
|
||||
Log.d(TAG, "Autonomous Iteration: ${iterations + 1}")
|
||||
val posts = commentReader.extractPostsAndComments()
|
||||
|
||||
if (posts.isNotEmpty()) {
|
||||
Log.d(TAG, "Found ${posts.size} posts. Sending to Gemini for reporting...")
|
||||
SocialBotClient.evaluatePosts(posts)
|
||||
}
|
||||
|
||||
Log.d(TAG, "Scrolling down for more posts...")
|
||||
navigator.scrollForward()
|
||||
delay(4000) // wait for new posts to load
|
||||
iterations++
|
||||
}
|
||||
|
||||
SocialBotClient.completeTask(task.id, "Autonomous session completed")
|
||||
}
|
||||
"read_posts" -> {
|
||||
// Logic to read posts and send to backend
|
||||
Log.d(TAG, "Starting to scan screen for posts/comments...")
|
||||
|
||||
+74
-10
@@ -11,37 +11,72 @@ import kotlinx.coroutines.delay
|
||||
class FacebookCommentPoster(private val service: AccessibilityService) {
|
||||
|
||||
suspend fun postComment(commentText: String): Boolean {
|
||||
return performCommentPosting(commentText)
|
||||
}
|
||||
|
||||
suspend fun postCommentOnSpecificPost(originalText: String, commentText: String): Boolean {
|
||||
val root = service.rootInActiveWindow ?: return false
|
||||
|
||||
// 1. Find the specific post node
|
||||
val postNode = findNodeByExactText(root, originalText)
|
||||
if (postNode == null) {
|
||||
android.util.Log.e("FacebookCommentPoster", "Could not find original post text on screen")
|
||||
return false
|
||||
}
|
||||
|
||||
// 2. Find the comment button associated with this post
|
||||
// We go up to the parent container and look for a "Comment" or "تعليق" button
|
||||
var commentBtn = findCommentButtonNearby(postNode)
|
||||
|
||||
if (commentBtn == null) {
|
||||
// Fallback: just search the whole screen for the first comment button
|
||||
commentBtn = findNodeByContentDescription(root, "Comment") ?: findNodeByContentDescription(root, "تعليق")
|
||||
}
|
||||
|
||||
if (commentBtn != null) {
|
||||
commentBtn.performAction(AccessibilityNodeInfo.ACTION_CLICK)
|
||||
delay(3000) // wait for comment overlay to open
|
||||
|
||||
val success = performCommentPosting(commentText)
|
||||
|
||||
// Go back to feed
|
||||
service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK)
|
||||
delay(2000)
|
||||
|
||||
return success
|
||||
}
|
||||
|
||||
android.util.Log.e("FacebookCommentPoster", "Could not find comment button")
|
||||
return false
|
||||
}
|
||||
|
||||
private suspend fun performCommentPosting(commentText: String): Boolean {
|
||||
val root = service.rootInActiveWindow ?: return false
|
||||
|
||||
// 1. Find the comment input box.
|
||||
// In Facebook, it might have contentDescription like "Write a comment..." or similar.
|
||||
val inputNode = findNodeByContentDescription(root, "Write a comment")
|
||||
?: findNodeByContentDescription(root, "اكتب تعليق")
|
||||
?: findNodeByClassName(root, "android.widget.EditText")
|
||||
|
||||
if (inputNode == null) return false
|
||||
|
||||
// 2. Click the input node to focus it
|
||||
inputNode.performAction(AccessibilityNodeInfo.ACTION_CLICK)
|
||||
delay(1000) // wait for keyboard
|
||||
delay(1000)
|
||||
|
||||
// 3. Set text (simulating human typing can be done by pasting chunks or using set_text)
|
||||
val arguments = Bundle()
|
||||
arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, commentText)
|
||||
inputNode.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments)
|
||||
|
||||
// Wait a bit to simulate human typing delay
|
||||
val randomTypingDelay = (commentText.length * 50).toLong().coerceAtMost(5000L)
|
||||
val randomTypingDelay = (commentText.length * 50).toLong().coerceAtMost(4000L)
|
||||
delay(randomTypingDelay)
|
||||
|
||||
// 4. Find and click the send button
|
||||
// Often has a content description like "Send" or it's an ImageView next to EditText
|
||||
val sendBtn = findNodeByContentDescription(service.rootInActiveWindow, "Send")
|
||||
?: findNodeByContentDescription(service.rootInActiveWindow, "إرسال")
|
||||
|
||||
if (sendBtn != null) {
|
||||
sendBtn.performAction(AccessibilityNodeInfo.ACTION_CLICK)
|
||||
delay(2000)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -73,4 +108,33 @@ class FacebookCommentPoster(private val service: AccessibilityService) {
|
||||
}
|
||||
return null
|
||||
}
|
||||
private fun findNodeByExactText(node: AccessibilityNodeInfo?, text: String): AccessibilityNodeInfo? {
|
||||
if (node == null) return null
|
||||
|
||||
val nodeText = node.text?.toString()?.trim() ?: node.contentDescription?.toString()?.trim()
|
||||
if (nodeText == text.trim()) {
|
||||
return node
|
||||
}
|
||||
|
||||
for (i in 0 until node.childCount) {
|
||||
val child = findNodeByExactText(node.getChild(i), text)
|
||||
if (child != null) return child
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findCommentButtonNearby(postNode: AccessibilityNodeInfo): AccessibilityNodeInfo? {
|
||||
// Go up the view hierarchy to find the container
|
||||
var parent = postNode.parent
|
||||
var depth = 0
|
||||
while (parent != null && depth < 5) {
|
||||
val btn = findNodeByContentDescription(parent, "Comment")
|
||||
?: findNodeByContentDescription(parent, "تعليق")
|
||||
if (btn != null) return btn
|
||||
|
||||
parent = parent.parent
|
||||
depth++
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
+16
-1
@@ -50,7 +50,8 @@ class FacebookCommentReader(private val service: AccessibilityService) {
|
||||
"watch", "marketplace", "groups", "notifications", "menu",
|
||||
"إعجاب", "تعليق", "مشاركة", "إرسال", "اكتب تعليقاً",
|
||||
"رد", "عرض المزيد من التعليقات", "الأكثر صلة", "الصفحة الرئيسية",
|
||||
"المجموعات", "الإشعارات", "القائمة", "Sponsored", "مُموَّل"
|
||||
"المجموعات", "الإشعارات", "القائمة", "Sponsored", "مُموَّل",
|
||||
"انتقال إلى الملف الشخصي", "اكتب تعليقًا...", "بحث في المجموعات"
|
||||
)
|
||||
|
||||
for (ignore in ignoreList) {
|
||||
@@ -59,6 +60,20 @@ class FacebookCommentReader(private val service: AccessibilityService) {
|
||||
}
|
||||
}
|
||||
|
||||
// 2.5 Ignore UI patterns (Partial matches)
|
||||
val ignorePatterns = listOf(
|
||||
"علامة التبويب", "صورة ملف", "اقتراح", "لم تتم مشاهدتها",
|
||||
"خيارات إضافية", "عرض المزيد من الخيارات", "تحديد صور أو مقاطع فيديو",
|
||||
"إنشاء، اضغط ضغطًا مزدوجًا", "إزالة اقتراح", "عرض كل اقتراحات",
|
||||
"زر أعجبني", "الزر مشاركة", "Action chip profile picture"
|
||||
)
|
||||
|
||||
for (pattern in ignorePatterns) {
|
||||
if (trimmed.contains(pattern, ignoreCase = true)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Ignore texts that look like timestamps or metrics (e.g. "2 hrs", "10K Likes")
|
||||
// Simplistic check: if it's short and ends with common time units
|
||||
if (trimmed.length < 25) {
|
||||
|
||||
+63
@@ -23,4 +23,67 @@ class FacebookNavigator(private val service: AccessibilityService) {
|
||||
// Wait for app to open
|
||||
delay(3000)
|
||||
}
|
||||
|
||||
suspend fun openApp() {
|
||||
val launchIntent = service.packageManager.getLaunchIntentForPackage("com.facebook.katana")
|
||||
if (launchIntent != null) {
|
||||
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
service.startActivity(launchIntent)
|
||||
delay(5000) // Wait 5 seconds for Facebook to load completely
|
||||
|
||||
// Try to navigate to Groups Tab automatically
|
||||
openGroupsTab()
|
||||
} else {
|
||||
android.util.Log.e("FacebookNavigator", "Facebook app is not installed.")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun openGroupsTab() {
|
||||
val root = service.rootInActiveWindow ?: return
|
||||
|
||||
// Search for Groups tab
|
||||
val groupsTab = findNodeByContentDescription(root, "المجموعات")
|
||||
?: findNodeByContentDescription(root, "Groups")
|
||||
|
||||
if (groupsTab != null) {
|
||||
groupsTab.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK)
|
||||
delay(3000) // wait for Groups tab to load
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun scrollForward(): Boolean {
|
||||
val root = service.rootInActiveWindow ?: return false
|
||||
return performScroll(root)
|
||||
}
|
||||
|
||||
private fun performScroll(node: android.view.accessibility.AccessibilityNodeInfo?): Boolean {
|
||||
if (node == null) return false
|
||||
|
||||
if (node.isScrollable && node.className?.toString()?.contains("RecyclerView") == true) {
|
||||
val scrolled = node.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)
|
||||
if (scrolled) return true
|
||||
}
|
||||
|
||||
for (i in 0 until node.childCount) {
|
||||
if (performScroll(node.getChild(i))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun findNodeByContentDescription(node: android.view.accessibility.AccessibilityNodeInfo?, descPrefix: String): android.view.accessibility.AccessibilityNodeInfo? {
|
||||
if (node == null) return null
|
||||
|
||||
if (node.contentDescription?.startsWith(descPrefix, ignoreCase = true) == true ||
|
||||
node.text?.startsWith(descPrefix, ignoreCase = true) == true) {
|
||||
return node
|
||||
}
|
||||
|
||||
for (i in 0 until node.childCount) {
|
||||
val child = findNodeByContentDescription(node.getChild(i), descPrefix)
|
||||
if (child != null) return child
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user