Compare commits
6
Commits
c322e92913
...
0c19486a24
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c19486a24 | ||
|
|
03a74fa106 | ||
|
|
27b8a8ec59 | ||
|
|
4a6ca417ad | ||
|
|
36761dce91 | ||
|
|
632ff070fd |
@@ -24,6 +24,8 @@ import requests
|
||||
SERVER_URL = "http://jordan-siro.intaleqapp.com/backend/marketing_engine/social_worker.php"
|
||||
BOT_TOKEN = "YOUR_SECRET_BOT_TOKEN" # Must match headers in social_worker.php
|
||||
PLATFORM = "facebook" # 'facebook' or 'telegram'
|
||||
ACCOUNT_ID = None # Set to a specific integer if manual mapping is preferred
|
||||
DEVICE_ID = None # e.g. "a1b2c3d4" - Recommended: The unique Android device ID for auto Plug & Play binding
|
||||
CHECK_INTERVAL_SECONDS = 60 # Cooldown between checking for new tasks
|
||||
|
||||
headers = {
|
||||
@@ -105,7 +107,13 @@ def report_status(action, params):
|
||||
def process_task():
|
||||
print("[*] Checking for pending tasks...")
|
||||
try:
|
||||
res = requests.get(f"{SERVER_URL}?action=get_task&platform={PLATFORM}", headers=headers, timeout=10)
|
||||
url = f"{SERVER_URL}?action=get_task&platform={PLATFORM}"
|
||||
if ACCOUNT_ID:
|
||||
url += f"&account_id={ACCOUNT_ID}"
|
||||
elif DEVICE_ID:
|
||||
url += f"&device_id={DEVICE_ID}"
|
||||
|
||||
res = requests.get(url, headers=headers, timeout=10)
|
||||
response_data = res.json()
|
||||
except Exception as e:
|
||||
print(f"[-] Connection failed: {e}")
|
||||
|
||||
@@ -63,6 +63,8 @@ function generateCommentWithGemini($postContext, $intent) {
|
||||
|
||||
if ($intent === 'promote_siro') {
|
||||
$prompt .= "المطلوب: اكتب تعليقاً تتعاطف فيه مع المشكلة المذكورة (إن وجدت)، وانصح الزملاء بتجربة تطبيق 'سيرو' كحل واقعي وممتاز لكباتن النقل الذكي. اجعل التعليق قصيراً ومباشراً.";
|
||||
} elseif ($intent === 'soft_promote_siro') {
|
||||
$prompt .= "المطلوب: تفاعل مع المنشور بشكل طبيعي جداً وعفوي، شارك رأيك أو تعاطف مع المشكلة. قم بالتلميح بشكل خفيف جداً وعابر لتطبيق 'سيرو' كخيار واعد مستقبلاً دون أن تبدو كإعلان، لأن التطبيق لم يطلق رسمياً بعد. اجعله يبدو كحديث دردشة طبيعية.";
|
||||
} elseif ($intent === 'answer_question') {
|
||||
$prompt .= "المطلوب: أجب على السؤال المطروح في المنشور بوضوح بناءً على خبرتك مع تطبيق 'سيرو'.";
|
||||
} elseif ($intent === 'dialogue_skeptic') {
|
||||
|
||||
@@ -26,22 +26,65 @@ try {
|
||||
case 'get_task':
|
||||
// The bot asks for a task to do
|
||||
$platform = $_GET['platform'] ?? 'facebook';
|
||||
$requestedAccountId = $_GET['account_id'] ?? null;
|
||||
$deviceId = $_GET['device_id'] ?? null;
|
||||
|
||||
// Automatic Plug & Play Device Binding
|
||||
if ($deviceId) {
|
||||
$stmtDev = $con->prepare("SELECT id FROM social_accounts WHERE device_id = ?");
|
||||
$stmtDev->execute([$deviceId]);
|
||||
$acc = $stmtDev->fetch(PDO::FETCH_ASSOC);
|
||||
if ($acc) {
|
||||
$requestedAccountId = $acc['id'];
|
||||
} else {
|
||||
require_once __DIR__ . '/account_manager.php';
|
||||
$am = new AccountManager();
|
||||
// Find an account that doesn't have a device_id yet
|
||||
$stmtFind = $con->prepare("SELECT id FROM social_accounts WHERE platform = ? AND device_id IS NULL AND status = 'active' LIMIT 1");
|
||||
$stmtFind->execute([$platform]);
|
||||
$newAcc = $stmtFind->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($newAcc) {
|
||||
$requestedAccountId = $newAcc['id'];
|
||||
$updateDev = $con->prepare("UPDATE social_accounts SET device_id = ? WHERE id = ?");
|
||||
$updateDev->execute([$deviceId, $requestedAccountId]);
|
||||
} else {
|
||||
echo json_encode(['status' => 'error', 'message' => 'No available unassigned accounts for this new device']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find a pending task that is scheduled for now or earlier
|
||||
$stmt = $con->prepare("
|
||||
SELECT id, account_id, type, target_url, prompt_context, generated_comment
|
||||
FROM social_tasks
|
||||
WHERE status = 'pending' AND platform = ? AND (scheduled_at IS NULL OR scheduled_at <= NOW())
|
||||
ORDER BY created_at ASC LIMIT 1
|
||||
");
|
||||
$stmt->execute([$platform]);
|
||||
if ($requestedAccountId) {
|
||||
// If the phone is strictly tied to one account, fetch a task specifically for it
|
||||
// Or fetch an unassigned task and assign it to this phone's account
|
||||
$stmt = $con->prepare("
|
||||
SELECT id, account_id, type, target_url, prompt_context, generated_comment
|
||||
FROM social_tasks
|
||||
WHERE status = 'pending'
|
||||
AND platform = ?
|
||||
AND (account_id = ? OR account_id IS NULL)
|
||||
AND (scheduled_at IS NULL OR scheduled_at <= NOW())
|
||||
ORDER BY created_at ASC LIMIT 1
|
||||
");
|
||||
$stmt->execute([$platform, $requestedAccountId]);
|
||||
} else {
|
||||
$stmt = $con->prepare("
|
||||
SELECT id, account_id, type, target_url, prompt_context, generated_comment
|
||||
FROM social_tasks
|
||||
WHERE status = 'pending' AND platform = ? AND (scheduled_at IS NULL OR scheduled_at <= NOW())
|
||||
ORDER BY created_at ASC LIMIT 1
|
||||
");
|
||||
$stmt->execute([$platform]);
|
||||
}
|
||||
$task = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($task) {
|
||||
require_once __DIR__ . '/account_manager.php';
|
||||
$am = new AccountManager();
|
||||
|
||||
$accountId = $task['account_id'];
|
||||
$accountId = $task['account_id'] ?: $requestedAccountId;
|
||||
$account = null;
|
||||
|
||||
if ($accountId) {
|
||||
@@ -52,6 +95,12 @@ try {
|
||||
");
|
||||
$stmtAcc->execute([$accountId]);
|
||||
$account = $stmtAcc->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($account && !$task['account_id']) {
|
||||
$updateTask = $con->prepare("UPDATE social_tasks SET account_id = ? WHERE id = ?");
|
||||
$updateTask->execute([$accountId, $task['id']]);
|
||||
$task['account_id'] = $accountId;
|
||||
}
|
||||
} else {
|
||||
// Dynamically get an available account and assign it
|
||||
$account = $am->getAvailableAccount($platform, 15); // 15 min cooldown
|
||||
@@ -90,6 +139,74 @@ try {
|
||||
} else {
|
||||
echo json_encode(['status' => 'success', 'message' => 'No tasks available', 'data' => null]);
|
||||
}
|
||||
case 'process_organic_post':
|
||||
// The bot found a post organically via Accessibility, copied its link, and needs a comment NOW
|
||||
$platform = $_POST['platform'] ?? 'facebook';
|
||||
$deviceId = $_POST['device_id'] ?? null;
|
||||
$targetUrl = $_POST['target_url'] ?? null;
|
||||
$postText = $_POST['post_text'] ?? '';
|
||||
|
||||
if (!$deviceId || !$targetUrl || !$postText) {
|
||||
echo json_encode(['status' => 'error', 'message' => 'device_id, target_url, and post_text are required']);
|
||||
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
|
||||
$stmtDev = $con->prepare("SELECT id FROM social_accounts WHERE device_id = ?");
|
||||
$stmtDev->execute([$deviceId]);
|
||||
$acc = $stmtDev->fetch(PDO::FETCH_ASSOC);
|
||||
$accountId = $acc ? $acc['id'] : null;
|
||||
|
||||
if (!$accountId) {
|
||||
// Not registered yet, auto-bind
|
||||
require_once __DIR__ . '/account_manager.php';
|
||||
$am = new AccountManager();
|
||||
$stmtFind = $con->prepare("SELECT id FROM social_accounts WHERE platform = ? AND device_id IS NULL AND status = 'active' LIMIT 1");
|
||||
$stmtFind->execute([$platform]);
|
||||
$newAcc = $stmtFind->fetch(PDO::FETCH_ASSOC);
|
||||
if ($newAcc) {
|
||||
$accountId = $newAcc['id'];
|
||||
$updateDev = $con->prepare("UPDATE social_accounts SET device_id = ? WHERE id = ?");
|
||||
$updateDev->execute([$deviceId, $accountId]);
|
||||
} else {
|
||||
echo json_encode(['status' => 'error', 'message' => 'No accounts available for new device']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Generate Immediate Comment
|
||||
require_once __DIR__ . '/gemini_comment_generator.php';
|
||||
// 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
|
||||
$stmt = $con->prepare("
|
||||
INSERT INTO social_tasks (account_id, platform, type, target_url, prompt_context, generated_comment, status, completed_at)
|
||||
VALUES (?, ?, 'post_comment', ?, ?, ?, 'completed', NOW())
|
||||
");
|
||||
$stmt->execute([$accountId, $platform, $targetUrl, $postText, $generatedComment]);
|
||||
|
||||
// 4. Schedule Drama (Supporting Accounts)
|
||||
require_once __DIR__ . '/schedule_manager.php';
|
||||
$sm = new ScheduleManager();
|
||||
$sm->scheduleDialogueDrama($platform, $targetUrl, $postText, $accountId, 5); // 5 mins delay
|
||||
|
||||
// 5. Return the generated comment immediately to the Android Bot
|
||||
echo json_encode([
|
||||
'status' => 'success',
|
||||
'data' => [
|
||||
'generated_comment' => $generatedComment,
|
||||
'target_url' => $targetUrl
|
||||
]
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'complete_task':
|
||||
|
||||
@@ -2083,6 +2083,11 @@ CREATE TABLE IF NOT EXISTS `social_accounts` (
|
||||
`total_posts` INT DEFAULT 0,
|
||||
`total_comments` INT DEFAULT 0,
|
||||
`total_videos` INT DEFAULT 0,
|
||||
`proxy_ip` VARCHAR(100) NULL,
|
||||
`proxy_port` INT NULL,
|
||||
`proxy_username` VARCHAR(100) NULL,
|
||||
`proxy_password` VARCHAR(100) NULL,
|
||||
`device_id` VARCHAR(255) NULL UNIQUE,
|
||||
`last_active` DATETIME NULL,
|
||||
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
|
||||
@@ -91,6 +91,40 @@ object SocialBotClient {
|
||||
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) {
|
||||
postData("action=fail_task", "task_id=$taskId&error_message=$errorMessage")
|
||||
}
|
||||
|
||||
+48
-36
@@ -99,54 +99,72 @@ class FacebookBotService : AccessibilityService() {
|
||||
navigator.openApp()
|
||||
delay(3000)
|
||||
|
||||
val allCollectedTexts = mutableSetOf<String>()
|
||||
val totalIterations = 20 // 20 iterations * ~15 sec = ~5 minutes of scrolling
|
||||
val deviceId = android.provider.Settings.Secure.getString(contentResolver, android.provider.Settings.Secure.ANDROID_ID)
|
||||
val totalIterations = 20
|
||||
var iterations = 0
|
||||
var consecutiveNoNewPosts = 0
|
||||
var lastProcessedPostText = ""
|
||||
|
||||
while (iterations < totalIterations) {
|
||||
Log.d(TAG, "Autonomous Iteration: ${iterations + 1} of $totalIterations")
|
||||
val initialSize = allCollectedTexts.size
|
||||
|
||||
// 1. Expand texts
|
||||
commentReader.expandPostText()
|
||||
delay(1000)
|
||||
|
||||
// 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 (commentReader.openComments()) {
|
||||
Log.d(TAG, "Opened comments section. Reading...")
|
||||
delay(4000) // wait for bottom sheet to load
|
||||
if (currentPost != null) {
|
||||
Log.d(TAG, "Found valid post: \n$currentPost")
|
||||
lastProcessedPostText = currentPost
|
||||
consecutiveNoNewPosts = 0
|
||||
|
||||
// 4. Expand long comments
|
||||
commentReader.expandPostText()
|
||||
delay(1000)
|
||||
// 3. Attempt to copy link
|
||||
val linkCopied = navigator.copyPostLink()
|
||||
|
||||
// 5. Read comments
|
||||
val comments = commentReader.extractPostsAndComments().map { "[COMMENT]: $it" }
|
||||
posts.addAll(comments)
|
||||
|
||||
// 6. Go back to feed
|
||||
Log.d(TAG, "Going back to main feed...")
|
||||
navigator.goBack()
|
||||
delay(3000)
|
||||
}
|
||||
|
||||
// Add to our global collection (Set avoids duplicates)
|
||||
allCollectedTexts.addAll(posts)
|
||||
Log.d(TAG, "Collected ${posts.size} texts in this iteration. Total unique so far: ${allCollectedTexts.size}")
|
||||
|
||||
if (allCollectedTexts.size == initialSize) {
|
||||
if (linkCopied) {
|
||||
// 4. Get copied link from clipboard
|
||||
val clipboardManager = getSystemService(android.content.Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
|
||||
val clipboardText = clipboardManager.primaryClip?.getItemAt(0)?.text?.toString() ?: ""
|
||||
|
||||
if (clipboardText.contains("http", ignoreCase = true)) {
|
||||
Log.d(TAG, "Copied Link: $clipboardText")
|
||||
|
||||
// 5. Send to Server for organic processing
|
||||
Log.d(TAG, "Sending to server for organic processing...")
|
||||
val responseData = SocialBotClient.processOrganicPost(deviceId, "facebook", currentPost, clipboardText)
|
||||
|
||||
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++
|
||||
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.")
|
||||
Log.d(TAG, "No new posts for 3 consecutive iterations. Breaking early.")
|
||||
break
|
||||
}
|
||||
} else {
|
||||
consecutiveNoNewPosts = 0
|
||||
}
|
||||
|
||||
Log.d(TAG, "Scrolling down for more posts...")
|
||||
@@ -155,13 +173,7 @@ class FacebookBotService : AccessibilityService() {
|
||||
iterations++
|
||||
}
|
||||
|
||||
// Send everything to Gemini AT ONCE to save API costs and generate a comprehensive report
|
||||
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.")
|
||||
SocialBotClient.completeTask(task.id, "Autonomous session completed.")
|
||||
}
|
||||
"read_posts" -> {
|
||||
// Logic to read posts and send to backend
|
||||
|
||||
+110
@@ -207,4 +207,114 @@ class FacebookNavigator(private val service: AccessibilityService) {
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun createPost(text: String): Boolean {
|
||||
val root = service.rootInActiveWindow ?: return false
|
||||
|
||||
// 1. Find "What's on your mind?" or "Write something..."
|
||||
val inputBoxNodes = mutableListOf<android.view.accessibility.AccessibilityNodeInfo>()
|
||||
findNodesByText(root, "بم تفكر؟", inputBoxNodes)
|
||||
findNodesByText(root, "What's on your mind", inputBoxNodes)
|
||||
findNodesByText(root, "اكتب شيئًا", inputBoxNodes)
|
||||
findNodesByText(root, "Write something", inputBoxNodes)
|
||||
|
||||
if (inputBoxNodes.isEmpty()) {
|
||||
android.util.Log.d("FacebookNavigator", "Create post box not found")
|
||||
return false
|
||||
}
|
||||
|
||||
val inputBox = inputBoxNodes.first()
|
||||
inputBox.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK)
|
||||
kotlinx.coroutines.delay(3000) // Wait for Create Post screen to open
|
||||
|
||||
// 2. Find the EditText field
|
||||
val newRoot = service.rootInActiveWindow ?: return false
|
||||
val editTexts = mutableListOf<android.view.accessibility.AccessibilityNodeInfo>()
|
||||
findEditTexts(newRoot, editTexts)
|
||||
|
||||
if (editTexts.isEmpty()) {
|
||||
android.util.Log.d("FacebookNavigator", "EditText not found in Create Post screen")
|
||||
return false
|
||||
}
|
||||
|
||||
// Paste the text
|
||||
val editText = editTexts.first()
|
||||
val arguments = android.os.Bundle()
|
||||
arguments.putCharSequence(android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text)
|
||||
editText.performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT, arguments)
|
||||
kotlinx.coroutines.delay(1000)
|
||||
|
||||
// 3. Click "Post" (نشر)
|
||||
val postRoot = service.rootInActiveWindow ?: return false
|
||||
val postBtns = mutableListOf<android.view.accessibility.AccessibilityNodeInfo>()
|
||||
findNodesByText(postRoot, "نشر", postBtns)
|
||||
findNodesByText(postRoot, "Post", postBtns)
|
||||
|
||||
if (postBtns.isNotEmpty()) {
|
||||
postBtns.first().performAction(android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK)
|
||||
kotlinx.coroutines.delay(4000) // Wait for upload
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun findEditTexts(node: android.view.accessibility.AccessibilityNodeInfo?, list: MutableList<android.view.accessibility.AccessibilityNodeInfo>) {
|
||||
if (node == null) return
|
||||
if (node.className?.toString()?.contains("EditText") == true) {
|
||||
list.add(node)
|
||||
}
|
||||
for (i in 0 until node.childCount) {
|
||||
findEditTexts(node.getChild(i), list)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user