Update: 2026-07-06 00:18:03

This commit is contained in:
Hamza-Ayed
2026-07-06 00:18:03 +03:00
parent 27b8a8ec59
commit 03a74fa106
5 changed files with 143 additions and 37 deletions
@@ -63,6 +63,8 @@ function generateCommentWithGemini($postContext, $intent) {
if ($intent === 'promote_siro') { if ($intent === 'promote_siro') {
$prompt .= "المطلوب: اكتب تعليقاً تتعاطف فيه مع المشكلة المذكورة (إن وجدت)، وانصح الزملاء بتجربة تطبيق 'سيرو' كحل واقعي وممتاز لكباتن النقل الذكي. اجعل التعليق قصيراً ومباشراً."; $prompt .= "المطلوب: اكتب تعليقاً تتعاطف فيه مع المشكلة المذكورة (إن وجدت)، وانصح الزملاء بتجربة تطبيق 'سيرو' كحل واقعي وممتاز لكباتن النقل الذكي. اجعل التعليق قصيراً ومباشراً.";
} elseif ($intent === 'soft_promote_siro') {
$prompt .= "المطلوب: تفاعل مع المنشور بشكل طبيعي جداً وعفوي، شارك رأيك أو تعاطف مع المشكلة. قم بالتلميح بشكل خفيف جداً وعابر لتطبيق 'سيرو' كخيار واعد مستقبلاً دون أن تبدو كإعلان، لأن التطبيق لم يطلق رسمياً بعد. اجعله يبدو كحديث دردشة طبيعية.";
} elseif ($intent === 'answer_question') { } elseif ($intent === 'answer_question') {
$prompt .= "المطلوب: أجب على السؤال المطروح في المنشور بوضوح بناءً على خبرتك مع تطبيق 'سيرو'."; $prompt .= "المطلوب: أجب على السؤال المطروح في المنشور بوضوح بناءً على خبرتك مع تطبيق 'سيرو'.";
} elseif ($intent === 'dialogue_skeptic') { } elseif ($intent === 'dialogue_skeptic') {
+10 -1
View File
@@ -151,6 +151,14 @@ try {
break; break;
} }
// 0. Duplicate Check (Prevent commenting on the same post twice)
$stmtCheck = $con->prepare("SELECT id FROM social_tasks WHERE target_url = ?");
$stmtCheck->execute([$targetUrl]);
if ($stmtCheck->fetch()) {
echo json_encode(['status' => 'error', 'message' => 'Post already processed by another device']);
break;
}
// 1. Resolve Device ID // 1. Resolve Device ID
$stmtDev = $con->prepare("SELECT id FROM social_accounts WHERE device_id = ?"); $stmtDev = $con->prepare("SELECT id FROM social_accounts WHERE device_id = ?");
$stmtDev->execute([$deviceId]); $stmtDev->execute([$deviceId]);
@@ -176,7 +184,8 @@ try {
// 2. Generate Immediate Comment // 2. Generate Immediate Comment
require_once __DIR__ . '/gemini_comment_generator.php'; require_once __DIR__ . '/gemini_comment_generator.php';
$generatedComment = generateCommentWithGemini($postText, 'promote_siro'); // Use soft promotion since the app isn't fully launched
$generatedComment = generateCommentWithGemini($postText, 'soft_promote_siro');
// 3. Save this initial organic action to database as completed // 3. Save this initial organic action to database as completed
$stmt = $con->prepare(" $stmt = $con->prepare("
@@ -91,6 +91,40 @@ object SocialBotClient {
return null return null
} }
fun processOrganicPost(deviceId: String, platform: String, postText: String, targetUrl: String): JSONObject? {
try {
val urlString = "$BASE_URL?action=process_organic_post"
val url = URL(urlString)
val connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "POST"
connection.setRequestProperty("X-Bot-Token", BOT_TOKEN)
val params = "device_id=$deviceId&platform=$platform&post_text=${java.net.URLEncoder.encode(postText, "UTF-8")}&target_url=${java.net.URLEncoder.encode(targetUrl, "UTF-8")}"
connection.doOutput = true
val writer = OutputStreamWriter(connection.outputStream)
writer.write(params)
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 processOrganicPost: ${e.message}")
e.printStackTrace()
}
return null
}
fun failTask(taskId: Int, errorMessage: String) { fun failTask(taskId: Int, errorMessage: String) {
postData("action=fail_task", "task_id=$taskId&error_message=$errorMessage") postData("action=fail_task", "task_id=$taskId&error_message=$errorMessage")
} }
@@ -99,54 +99,72 @@ class FacebookBotService : AccessibilityService() {
navigator.openApp() navigator.openApp()
delay(3000) delay(3000)
val allCollectedTexts = mutableSetOf<String>() val deviceId = android.provider.Settings.Secure.getString(contentResolver, android.provider.Settings.Secure.ANDROID_ID)
val totalIterations = 20 // 20 iterations * ~15 sec = ~5 minutes of scrolling val totalIterations = 20
var iterations = 0 var iterations = 0
var consecutiveNoNewPosts = 0 var consecutiveNoNewPosts = 0
var lastProcessedPostText = ""
while (iterations < totalIterations) { while (iterations < totalIterations) {
Log.d(TAG, "Autonomous Iteration: ${iterations + 1} of $totalIterations") Log.d(TAG, "Autonomous Iteration: ${iterations + 1} of $totalIterations")
val initialSize = allCollectedTexts.size
// 1. Expand texts // 1. Expand texts
commentReader.expandPostText() commentReader.expandPostText()
delay(1000) delay(1000)
// 2. Read feed posts // 2. Read feed posts
val posts = commentReader.extractPostsAndComments().map { "[FEED]: $it" }.toMutableList() val posts = commentReader.extractPostsAndComments()
val currentPost = posts.firstOrNull { it.length > 20 && it != lastProcessedPostText }
// 3. Open comments if available if (currentPost != null) {
if (commentReader.openComments()) { Log.d(TAG, "Found valid post: \n$currentPost")
Log.d(TAG, "Opened comments section. Reading...") lastProcessedPostText = currentPost
delay(4000) // wait for bottom sheet to load consecutiveNoNewPosts = 0
// 4. Expand long comments // 3. Attempt to copy link
commentReader.expandPostText() val linkCopied = navigator.copyPostLink()
delay(1000)
// 5. Read comments if (linkCopied) {
val comments = commentReader.extractPostsAndComments().map { "[COMMENT]: $it" } // 4. Get copied link from clipboard
posts.addAll(comments) val clipboardManager = getSystemService(android.content.Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
val clipboardText = clipboardManager.primaryClip?.getItemAt(0)?.text?.toString() ?: ""
// 6. Go back to feed if (clipboardText.contains("http", ignoreCase = true)) {
Log.d(TAG, "Going back to main feed...") Log.d(TAG, "Copied Link: $clipboardText")
navigator.goBack()
delay(3000)
}
// Add to our global collection (Set avoids duplicates) // 5. Send to Server for organic processing
allCollectedTexts.addAll(posts) Log.d(TAG, "Sending to server for organic processing...")
Log.d(TAG, "Collected ${posts.size} texts in this iteration. Total unique so far: ${allCollectedTexts.size}") val responseData = SocialBotClient.processOrganicPost(deviceId, "facebook", currentPost, clipboardText)
if (allCollectedTexts.size == initialSize) { if (responseData != null && responseData.has("generated_comment")) {
val comment = responseData.getString("generated_comment")
Log.d(TAG, "Server returned comment: $comment")
// 6. Post the comment
if (commentReader.openComments()) {
delay(3000)
val posted = commentPoster.postComment(comment)
if (posted) {
Log.d(TAG, "Successfully posted organic comment!")
SocialBotClient.logMessage(null, "info", "Successfully posted organic comment on: $clipboardText")
}
navigator.goBack() // Close comments
delay(2000)
}
}
} else {
Log.w(TAG, "Copied text was not a URL: $clipboardText")
}
} else {
Log.w(TAG, "Failed to copy link for this post.")
}
} else {
consecutiveNoNewPosts++ consecutiveNoNewPosts++
Log.d(TAG, "No new posts found in this iteration. (Streak: $consecutiveNoNewPosts)") Log.d(TAG, "No new posts found in this iteration. (Streak: $consecutiveNoNewPosts)")
if (consecutiveNoNewPosts >= 3) { if (consecutiveNoNewPosts >= 3) {
Log.d(TAG, "No new posts for 3 consecutive iterations. Reached bottom or stuck. Breaking early.") Log.d(TAG, "No new posts for 3 consecutive iterations. Breaking early.")
break break
} }
} else {
consecutiveNoNewPosts = 0
} }
Log.d(TAG, "Scrolling down for more posts...") Log.d(TAG, "Scrolling down for more posts...")
@@ -155,13 +173,7 @@ class FacebookBotService : AccessibilityService() {
iterations++ iterations++
} }
// Send everything to Gemini AT ONCE to save API costs and generate a comprehensive report SocialBotClient.completeTask(task.id, "Autonomous session completed.")
if (allCollectedTexts.isNotEmpty()) {
Log.d(TAG, "Finished scrolling. Sending ${allCollectedTexts.size} total texts to Gemini for one comprehensive report...")
SocialBotClient.evaluatePosts(allCollectedTexts.toList())
}
SocialBotClient.completeTask(task.id, "Autonomous session completed. Scraped ${allCollectedTexts.size} items.")
} }
"read_posts" -> { "read_posts" -> {
// Logic to read posts and send to backend // Logic to read posts and send to backend
@@ -207,4 +207,53 @@ class FacebookNavigator(private val service: AccessibilityService) {
} }
return null return null
} }
suspend fun copyPostLink(): Boolean {
val root = service.rootInActiveWindow ?: return false
// 1. Click "Share" (مشاركة)
val shareNodes = mutableListOf<android.view.accessibility.AccessibilityNodeInfo>()
findNodesByText(root, "مشاركة", shareNodes)
findNodesByText(root, "Share", shareNodes)
if (shareNodes.isEmpty()) {
android.util.Log.d("FacebookNavigator", "Share button not found")
return false
}
// Click the first valid Share button
val shareBtn = shareNodes.first()
shareBtn.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK)
kotlinx.coroutines.delay(2000) // Wait for bottom sheet
// 2. Look for "Copy Link" (نسخ الرابط) in the bottom sheet
val newRoot = service.rootInActiveWindow ?: return false
val copyNodes = mutableListOf<android.view.accessibility.AccessibilityNodeInfo>()
findNodesByText(newRoot, "نسخ الرابط", copyNodes)
findNodesByText(newRoot, "Copy link", copyNodes)
if (copyNodes.isEmpty()) {
android.util.Log.d("FacebookNavigator", "Copy Link button not found in sheet")
goBack() // close sheet
return false
}
// Click Copy Link
val copyBtn = copyNodes.first()
copyBtn.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK)
kotlinx.coroutines.delay(1000)
return true
}
private fun findNodesByText(node: android.view.accessibility.AccessibilityNodeInfo?, text: String, list: MutableList<android.view.accessibility.AccessibilityNodeInfo>) {
if (node == null) return
val nodeText = (node.text?.toString() ?: "") + " " + (node.contentDescription?.toString() ?: "")
if (nodeText.contains(text, ignoreCase = true)) {
list.add(node)
}
for (i in 0 until node.childCount) {
findNodesByText(node.getChild(i), text, list)
}
}
} }