diff --git a/backend/marketing_engine/account_manager.php b/backend/marketing_engine/account_manager.php index 610a8b30..58aff993 100644 --- a/backend/marketing_engine/account_manager.php +++ b/backend/marketing_engine/account_manager.php @@ -26,7 +26,7 @@ class AccountManager { // Order by last_active ascending to rotate through them (least recently used first). $stmt = $this->con->prepare(" - SELECT id, username, total_posts, total_comments + SELECT id, username, total_posts, total_comments, proxy_ip, proxy_port, proxy_username, proxy_password FROM social_accounts WHERE platform = ? AND status = 'active' diff --git a/backend/marketing_engine/client_bot_template.py b/backend/marketing_engine/client_bot_template.py new file mode 100644 index 00000000..6ba75c57 --- /dev/null +++ b/backend/marketing_engine/client_bot_template.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# ============================================================================== +# Siro Autonomous Android Automation Client (Guerrilla Marketing Bot) +# ============================================================================== +# This template automates Facebook/Telegram interactions on an Android device +# via ADB (Android Debug Bridge) with built-in proxy shifting and anti-ban delays. +# +# Requirements: +# 1. Python 3.x +# 2. Android device with USB Debugging enabled, connected to PC. +# 3. ADB installed and added to System PATH. +# 4. Target apps (Facebook/Telegram) installed and logged in. +# ============================================================================== + +import os +import sys +import time +import random +import subprocess +import requests + +# --- Configuration --- +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' +CHECK_INTERVAL_SECONDS = 60 # Cooldown between checking for new tasks + +headers = { + "X-Bot-Token": BOT_TOKEN +} + +# --- ADB Helper Functions --- +def run_adb(cmd): + """Run an ADB command and return output.""" + try: + result = subprocess.run(f"adb {cmd}", shell=True, capture_output=True, text=True, timeout=15) + return result.stdout.strip() + except subprocess.TimeoutExpired: + print("[-] ADB command timed out.") + return "" + +def set_android_proxy(ip, port, username=None, password=None): + """Dynamically set global HTTP proxy on Android device.""" + if not ip or not port: + print("[+] Clearing Android proxy...") + run_adb("shell settings put global http_proxy :0") + return + + print(f"[+] Routing traffic through proxy: {ip}:{port}...") + # Standard Android global proxy command + run_adb(f"shell settings put global http_proxy {ip}:{port}") + + # Note: If proxy requires auth, Android standard global proxy doesn't support it directly in shell. + # In that case, you must use a proxy client app like 'Postern' or 'Drony' on the device, + # or route through a local forwarder. + time.sleep(3) + +def adb_click(x, y): + """Click on coordinates (x, y).""" + run_adb(f"shell input tap {x} {y}") + time.sleep(random.uniform(1.0, 2.5)) + +def adb_type_humanlike(text): + """Simulate human typing with random delay between characters to evade ban engines.""" + print(f"[+] Typing: \"{text}\"") + # Clean text to prevent shell injection + safe_text = text.replace('"', '\\"').replace('$', '\\$').replace('`', '\\`').replace(' ', '%s') + + # Type characters in chunks to look natural + for char in text: + if char == ' ': + run_adb("shell input keyevent 62") # Space + else: + # We type characters, escaping special shell characters + escaped = char.replace('"', '\\"').replace('$', '\\$').replace('`', '\\`').replace("'", "\\'") + run_adb(f"shell input text '{escaped}'") + time.sleep(random.uniform(0.05, 0.25)) # Typing speed variability + + time.sleep(random.uniform(1.0, 2.0)) + +def adb_scroll_down(): + """Perform a human-like swipe down.""" + # swipe from x1 y1 to x2 y2 duration + x1, y1 = 500, 1500 + x2, y2 = 500, 600 + duration = random.randint(300, 800) + run_adb(f"shell input swipe {x1} {y1} {x2} {y2} {duration}") + time.sleep(random.uniform(1.5, 3.0)) + +def adb_open_url(url): + """Open a URL using Android's default browser/app handler (e.g. opens deep links).""" + print(f"[+] Launching URL: {url}") + run_adb(f"shell am start -a android.intent.action.VIEW -d '{url}'") + time.sleep(random.uniform(5.0, 8.0)) # Wait for app to load + +# --- Logging & Status Reporting --- +def report_status(action, params): + try: + requests.post(f"{SERVER_URL}?action={action}", data=params, headers=headers, timeout=10) + except Exception as e: + print(f"[-] Status report connection error: {e}") + +# --- Main Task Loop --- +def process_task(): + print("[*] Checking for pending tasks...") + try: + res = requests.get(f"{SERVER_URL}?action=get_task&platform={PLATFORM}", headers=headers, timeout=10) + response_data = res.json() + except Exception as e: + print(f"[-] Connection failed: {e}") + return + + if response_data.get("status") != "success" or not response_data.get("data"): + print("[*] No tasks scheduled at this moment.") + return + + task = response_data["data"] + + task_id = task["id"] + task_type = task["type"] + target_url = task["target_url"] + comment_text = task["generated_comment"] + + print(f"[+] Task Found! ID: {task_id} | Type: {task_type}") + print(f"[+] Account to use: {task.get('bot_username')}") + + # 1. Shifting IP via Proxy Configuration + set_android_proxy( + task.get("proxy_ip"), + task.get("proxy_port"), + task.get("proxy_username"), + task.get("proxy_password") + ) + + success = False + error_msg = "Unknown execution error" + + try: + # 2. Evading Automated Scanners via Random Pre-sleep + pre_delay = random.randint(5, 15) + print(f"[+] Simulating human delay before opening app... sleeping {pre_delay}s") + time.sleep(pre_delay) + + # 3. Open URL (Facebook Post/Telegram Link) + if target_url: + adb_open_url(target_url) + + # 4. Execute UI actions based on task type + if task_type == "post_comment" and comment_text: + # Note: Screen coordinates (x, y) vary by device screen size. + # You must customize these coordinates based on your device profile. + print("[*] Performing UI interactions to comment...") + adb_scroll_down() # Scroll to reveal comments area + + # Click on write comment box (Coordinates for Amman Drivers group screen layout) + # You can find coordinates by enabling 'Pointer Location' in Android Developer Settings. + adb_click(300, 1850) + + # Evade bot detectors by typing like a real person + adb_type_humanlike(comment_text) + + # Click send button (e.g. coordinate x: 1000, y: 1850) + adb_click(1010, 1850) + success = True + + elif task_type == "share_link": + print("[*] Sharing deep link...") + # Click Share coordinates, type message, and click post + # Example coordinates: + adb_click(900, 1200) # Share button + adb_click(500, 1500) # Write post option + adb_type_humanlike(comment_text) + adb_click(1000, 150) # Post button + success = True + + else: + error_msg = f"Task type {task_type} is not yet supported in Android ADB Client" + + except Exception as e: + error_msg = f"Automation failure: {str(e)}" + print(f"[-] Error: {error_msg}") + + # 5. Clear proxy setting to keep device clean + set_android_proxy(None, None) + + # 6. Report status back to Server + if success: + print("[+] Task executed successfully!") + report_status("complete_task", {"task_id": task_id, "result": "Comment posted naturally via ADB."}) + else: + print(f"[-] Task failed: {error_msg}") + report_status("fail_task", {"task_id": task_id, "error_message": error_msg}) + +if __name__ == "__main__": + print("[*] Starting Siro Guerrilla Marketing Automation Bot...") + # Basic check to ensure ADB is connected + devices = run_adb("devices") + if "device" not in devices.split("\n")[1:]: + print("[-] Critical Error: No Android device detected via ADB! Please connect device and enable USB Debugging.") + sys.exit(1) + + print("[+] Android device connected. Starting automation loop...") + while True: + process_task() + time.sleep(CHECK_INTERVAL_SECONDS) diff --git a/backend/marketing_engine/gemini_comment_generator.php b/backend/marketing_engine/gemini_comment_generator.php index 630a77ab..b7389c72 100644 --- a/backend/marketing_engine/gemini_comment_generator.php +++ b/backend/marketing_engine/gemini_comment_generator.php @@ -65,6 +65,10 @@ function generateCommentWithGemini($postContext, $intent) { $prompt .= "المطلوب: اكتب تعليقاً تتعاطف فيه مع المشكلة المذكورة (إن وجدت)، وانصح الزملاء بتجربة تطبيق 'سيرو' كحل واقعي وممتاز لكباتن النقل الذكي. اجعل التعليق قصيراً ومباشراً."; } elseif ($intent === 'answer_question') { $prompt .= "المطلوب: أجب على السؤال المطروح في المنشور بوضوح بناءً على خبرتك مع تطبيق 'سيرو'."; + } elseif ($intent === 'dialogue_skeptic') { + $prompt .= "المطلوب: اكتب تعليقاً يبدي شكاً بسيطاً أو فضولاً كزبون أو راكب عادي (مثال: هل التطبيق آمن للبنات؟ أو هل الأسعار حقيقية فعلاً؟ أو كيف كباتنهم؟)، اجعل التعليق قصيراً وسؤالاً طبيعياً وعفوياً جداً بالعامية."; + } elseif ($intent === 'dialogue_supporter') { + $prompt .= "المطلوب: أجب على الشك المذكور أو أكد التجربة الإيجابية بحماس كراكب/كابتن آخر جرب تطبيق سيرو (Siro) فعلاً ووجد فيه الأمان والخصم الجيد والأسعار الثابتة، مع كتابته بلهجة عامية دافئة وطبيعية جداً."; } else { $prompt .= "المطلوب: اكتب تعليقاً متفاعلاً بشكل عام مع المنشور بأسلوب السائقين."; } diff --git a/backend/marketing_engine/schedule_manager.php b/backend/marketing_engine/schedule_manager.php index 2c526cd6..8e55e5ba 100644 --- a/backend/marketing_engine/schedule_manager.php +++ b/backend/marketing_engine/schedule_manager.php @@ -47,8 +47,7 @@ class ScheduleManager { INSERT INTO social_tasks (account_id, platform, type, target_url, prompt_context, generated_comment, scheduled_at, status) VALUES (?, ?, ?, ?, ?, ?, ?, 'pending') "); - - return $stmt->execute([ + $success = $stmt->execute([ $accountId, $platform, $type, @@ -57,6 +56,61 @@ class ScheduleManager { $generatedComment, $scheduledAt ]); + + if ($success && ($type === 'share_link' || $type === 'post_comment')) { + // Retreive an active account to exclude as parent + $parentAccountId = $accountId; + if (!$parentAccountId) { + $stmtAcc = $this->con->prepare("SELECT id FROM social_accounts WHERE platform = ? AND status = 'active' LIMIT 1"); + $stmtAcc->execute([$platform]); + $parentAccountId = $stmtAcc->fetchColumn() ?: 0; + } + $this->scheduleDialogueDrama($platform, $targetUrl, $promptContext, $parentAccountId, $delayMinutes); + } + + return $success; + } + + /** + * Schedule supporting dialogue tasks from other accounts to create a buzz/drama. + */ + public function scheduleDialogueDrama($platform, $targetUrl, $promptContext, $parentAccountId, $baseDelayMinutes) { + // Fetch 2 other active accounts on the same platform + $stmt = $this->con->prepare(" + SELECT id FROM social_accounts + WHERE platform = ? AND status = 'active' AND id != ? + ORDER BY RAND() LIMIT 2 + "); + $stmt->execute([$platform, $parentAccountId]); + $accounts = $stmt->fetchAll(PDO::FETCH_COLUMN); + + if (count($accounts) < 1) return; + + // Account 2: Skeptic/Questioner + $accountId2 = $accounts[0]; + $comment2 = generateCommentWithGemini($promptContext, 'dialogue_skeptic'); + + $delay2 = $baseDelayMinutes + rand(3, 7); // Delay child task by rand(3, 7) minutes + $scheduledAt2 = date('Y-m-d H:i:s', strtotime("+$delay2 minutes")); + + $stmtInsert = $this->con->prepare(" + INSERT INTO social_tasks (account_id, platform, type, target_url, prompt_context, generated_comment, scheduled_at, status) + VALUES (?, ?, 'post_comment', ?, ?, ?, ?, 'pending') + "); + $stmtInsert->execute([$accountId2, $platform, $targetUrl, $promptContext, $comment2, $scheduledAt2]); + + // If we have a third account, schedule the supporter + if (count($accounts) >= 2) { + $accountId3 = $accounts[1]; + // Provide context containing the first comment so supporter replies to it + $augmentedContext = $promptContext . "\n[PREVIOUS_REPLY]: " . $comment2; + $comment3 = generateCommentWithGemini($augmentedContext, 'dialogue_supporter'); + + $delay3 = $delay2 + rand(4, 9); // Supporter replies after the skeptic + $scheduledAt3 = date('Y-m-d H:i:s', strtotime("+$delay3 minutes")); + + $stmtInsert->execute([$accountId3, $platform, $targetUrl, $promptContext, $comment3, $scheduledAt3]); + } } } diff --git a/backend/marketing_engine/schema.sql b/backend/marketing_engine/schema.sql index f4c26f60..a798c579 100644 --- a/backend/marketing_engine/schema.sql +++ b/backend/marketing_engine/schema.sql @@ -11,6 +11,10 @@ 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, `last_active` DATETIME NULL, `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, `updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP diff --git a/backend/marketing_engine/social_worker.php b/backend/marketing_engine/social_worker.php index 17d201dd..0ff6640e 100644 --- a/backend/marketing_engine/social_worker.php +++ b/backend/marketing_engine/social_worker.php @@ -29,7 +29,7 @@ try { // Find a pending task that is scheduled for now or earlier $stmt = $con->prepare(" - SELECT id, type, target_url, prompt_context, generated_comment + 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 @@ -38,11 +38,55 @@ try { $task = $stmt->fetch(PDO::FETCH_ASSOC); if ($task) { - // Mark as in progress + require_once __DIR__ . '/account_manager.php'; + $am = new AccountManager(); + + $accountId = $task['account_id']; + $account = null; + + if ($accountId) { + // Fetch this specific account details + $stmtAcc = $con->prepare(" + SELECT id, username, proxy_ip, proxy_port, proxy_username, proxy_password + FROM social_accounts WHERE id = ? + "); + $stmtAcc->execute([$accountId]); + $account = $stmtAcc->fetch(PDO::FETCH_ASSOC); + } else { + // Dynamically get an available account and assign it + $account = $am->getAvailableAccount($platform, 15); // 15 min cooldown + if ($account) { + $accountId = $account['id']; + $updateTask = $con->prepare("UPDATE social_tasks SET account_id = ? WHERE id = ?"); + $updateTask->execute([$accountId, $task['id']]); + $task['account_id'] = $accountId; + } + } + + if (!$account) { + echo json_encode(['status' => 'error', 'message' => 'No active social account available or in cooldown']); + break; + } + + if ($account) { + $task['bot_username'] = $account['username']; + $task['proxy_ip'] = $account['proxy_ip']; + $task['proxy_port'] = $account['proxy_port']; + $task['proxy_username'] = $account['proxy_username']; + $task['proxy_password'] = $account['proxy_password']; + } + + // Mark as in progress and update last active $update = $con->prepare("UPDATE social_tasks SET status = 'in_progress' WHERE id = ?"); $update->execute([$task['id']]); - echo json_encode(['status' => 'success', 'data' => $task]); + $am->markAccountActive($accountId); + + // Return task directly in 'data' to preserve compatibility with existing Android bots + echo json_encode([ + 'status' => 'success', + 'data' => $task + ]); } else { echo json_encode(['status' => 'success', 'message' => 'No tasks available', 'data' => null]); } diff --git a/backend/marketing_engine/test_system_flow.php b/backend/marketing_engine/test_system_flow.php new file mode 100644 index 00000000..ce44bb35 --- /dev/null +++ b/backend/marketing_engine/test_system_flow.php @@ -0,0 +1,113 @@ +getMessage() . "\n"); +} + +echo "[*] Starting Siro Integration Test...\n"; + +// --- 1. Set Up Mock Accounts --- +echo "[*] Setting up mock accounts in database...\n"; +// Clear old test accounts if they exist +$con->query("DELETE FROM social_accounts WHERE username LIKE 'test_bot_%'"); + +$stmt = $con->prepare(" + INSERT INTO social_accounts (platform, username, status, proxy_ip, proxy_port) + VALUES (?, ?, 'active', ?, ?) +"); +$stmt->execute(['facebook', 'test_bot_sy', '194.163.173.157', 8080]); +$stmt->execute(['facebook', 'test_bot_jo', '194.163.173.200', 8080]); +$stmt->execute(['facebook', 'test_bot_eg', '194.163.173.111', 8080]); + +echo "[+] 3 Mock accounts created successfully.\n"; + +// --- 2. Test Country Guessing & Dialogue Generation --- +$sm = new ScheduleManager(); + +$testCases = [ + [ + 'platform' => 'facebook', + 'url' => 'https://facebook.com/groups/yallago.syria/post/1', + 'context' => "شو رأيكم بتطبيق يلاغو في دمشق؟ الأسعار صايرة خيالية!", + 'expected_country' => 'SY' + ], + [ + 'platform' => 'facebook', + 'url' => 'https://facebook.com/groups/taxif.amman/post/2', + 'context' => "التاكسي إف لغى رحلتي مرتين اليوم بعمان، شو في بديل؟", + 'expected_country' => 'JO' + ] +]; + +foreach ($testCases as $index => $tc) { + echo "\n------------------------------------------------------------\n"; + echo "[*] Test Case " . ($index + 1) . ": Target URL " . $tc['url'] . "\n"; + + // Auto guess test + $guessed = guessCountryFromContext($tc['context']); + echo "[+] Expected Country: " . $tc['expected_country'] . " | Guessed: " . $guessed . "\n"; + if ($guessed === $tc['expected_country']) { + echo "[+] Country Detection: PASS\n"; + } else { + echo "[-] Country Detection: FAIL\n"; + } + + // Schedule Task + // This will schedule the parent task and automatically trigger the dialogue simulator (Drama) + echo "[*] Scheduling parent task and dialog drama...\n"; + $success = $sm->scheduleTask( + platform: $tc['platform'], + type: 'post_comment', + targetUrl: $tc['url'], + promptContext: $tc['context'], + accountId: null, + delayMinutes: 5 + ); + + if ($success) { + echo "[+] Initial task and supporting drama scheduled successfully.\n"; + } else { + echo "[-] Scheduling: FAIL\n"; + } +} + +// --- 3. Verify Scheduled Tasks in Database --- +echo "\n------------------------------------------------------------\n"; +echo "[*] Querying scheduled tasks to verify dialogue database records...\n"; + +$stmtTasks = $con->prepare(" + SELECT t.id, t.type, t.target_url, t.generated_comment, t.scheduled_at, a.username, a.proxy_ip + FROM social_tasks t + LEFT JOIN social_accounts a ON t.account_id = a.id + WHERE t.status = 'pending' AND t.target_url LIKE '%/post/%' + ORDER BY t.scheduled_at ASC +"); +$stmtTasks->execute(); +$tasks = $stmtTasks->fetchAll(PDO::FETCH_ASSOC); + +echo "[+] Found " . count($tasks) . " pending tasks in queue:\n"; +foreach ($tasks as $t) { + echo " - Account: [" . $t['username'] . " (IP: " . $t['proxy_ip'] . ")] | Type: " . $t['type'] . "\n"; + echo " Scheduled: " . $t['scheduled_at'] . "\n"; + echo " Generated Comment: \"" . trim($t['generated_comment']) . "\"\n\n"; +} + +// --- 4. Clean Up Mock Database Data --- +echo "[*] Cleaning up test data from DB...\n"; +$con->query("DELETE FROM social_tasks WHERE target_url LIKE '%/post/%'"); +$con->query("DELETE FROM social_accounts WHERE username LIKE 'test_bot_%'"); + +echo "\n[+] Siro Marketing Integration Test Completed Successfully!\n";