feat: استيراد كود سيرو إلى تريبز (سيرو @ecfe7568) — بلا تعديل

قرار المالك 2026-07-27: باك إند سيرو PHP هو المعتمد، وتطبيقاته المجرّبة
ميدانياً تحل محل إعادة البناء المؤرشفة. سيرو نفسه لم يُمسّ.

الخريطة:
  backend · payment_server · loction_server · ride_server ·
  passenger_server · docker · dashboard · stress_test  → الجذر
  siro_rider  → apps/rider          siro_driver  → apps/driver
  siro_admin  → dashboards/admin    siro_service → dashboards/service
  android_bot → apps/android_bot    socialBot    → apps/socialBot

نُسخ المتعقَّب في git سيرو فقط عبر `git archive` (3,198 ملفاً / ~169 م.ب)
لا `cp -r` — فاستُثنيت مخلفات البناء تلقائياً. بلا أي تعديل محتوى عمداً:
كل ما يلي يصير فرقاً مقروءاً مقابل المصدر.

لم يُستورد وسببه: siromove.com (الموقع التسويقي يبقى marketing/ في تريبز،
سيرو فيه 8 ملفات) · docs و planning (تريبز له docs/ الخاص) · deploy.sh
(ليس نشراً على سيرفر بل `git add . && git push origin --all` — فخّ في
مستودع آخر) · transit_dashboard (بانتظار قرار مصير backend-transit و
dashboards/transit-web).

⚠️ لا يبني بعد — ثلاثة نواقص متوقعة ومقصودة:
1. `.env` و `lib/env/env.g.dart` غير متعقَّبين في سيرو (أسرار لكل مستأجر):
   كل تطبيق فلاتر يحتاج .env خاصاً ثم توليد env.g.dart بـ build_runner.
2. إعدادات Firebase (9 ملفات google-services.json و GoogleService-Info.plist)
   يستبعدها .gitignore تريبز — ولكل مستأجر مشروع Firebase خاص أصلاً.
3. apps/driver في سيرو يشير إلى `../../Intaleq/packages/get` خارج المستودع →
   يجب ضمّ الحزم داخله أسوة بـ apps/rider.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hamza-Ayed
2026-07-27 05:14:13 +03:00
co-authored by Claude Opus 5
parent 9909d9b4c1
commit 4d8414c96b
3198 changed files with 766859 additions and 0 deletions
@@ -0,0 +1,67 @@
<?php
// ============================================================
// account_manager.php
// Handles rotation and selection of social media accounts
// ============================================================
require_once __DIR__ . '/../core/bootstrap.php';
class AccountManager {
private $con;
public function __construct() {
$this->con = Database::get('main');
}
/**
* Get an available account for a specific platform that hasn't been used too recently.
*
* @param string $platform 'facebook' or 'instagram'
* @param int $cooldownMinutes Minimum minutes since last active
* @return array|null Account details or null if none available
*/
public function getAvailableAccount($platform, $cooldownMinutes = 30) {
// Select an active account that was last active more than X minutes ago,
// or has never been active (last_active is NULL).
// Order by last_active ascending to rotate through them (least recently used first).
$stmt = $this->con->prepare("
SELECT id, username, total_posts, total_comments, proxy_ip, proxy_port, proxy_username, proxy_password
FROM social_accounts
WHERE platform = ?
AND status = 'active'
AND (last_active IS NULL OR last_active <= DATE_SUB(NOW(), INTERVAL ? MINUTE))
ORDER BY last_active ASC, id ASC
LIMIT 1
");
$stmt->execute([$platform, $cooldownMinutes]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
/**
* Update the last active timestamp for an account
*/
public function markAccountActive($accountId) {
$stmt = $this->con->prepare("UPDATE social_accounts SET last_active = NOW() WHERE id = ?");
$stmt->execute([$accountId]);
}
/**
* Increment comment count
*/
public function incrementCommentCount($accountId) {
$stmt = $this->con->prepare("UPDATE social_accounts SET total_comments = total_comments + 1, last_active = NOW() WHERE id = ?");
$stmt->execute([$accountId]);
}
/**
* Mark an account as restricted or banned
*/
public function markAccountStatus($accountId, $status) {
if (!in_array($status, ['active', 'restricted', 'banned'])) return false;
$stmt = $this->con->prepare("UPDATE social_accounts SET status = ? WHERE id = ?");
return $stmt->execute([$status, $accountId]);
}
}
@@ -0,0 +1,214 @@
#!/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'
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 = {
"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:
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}")
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)
@@ -0,0 +1,13 @@
<?php
require_once __DIR__ . '/../core/bootstrap.php';
$con = Database::get('main');
// Insert the autonomous_scroll_and_reply task for each platform
$platforms = ['facebook', 'instagram'];
foreach ($platforms as $platform) {
$stmt = $con->prepare("INSERT INTO marketing_tasks (platform, type, status) VALUES (?, 'autonomous_scroll_and_reply', 'pending')");
$stmt->execute([$platform]);
}
echo "Tasks inserted successfully at " . date('Y-m-d H:i:s') . "\n";
@@ -0,0 +1,87 @@
<?php
// ============================================================
// marketing_engine/cron_weekly_report.php
// Script to be run via cron (e.g. weekly) to summarize all reports
// ============================================================
require_once __DIR__ . '/../core/bootstrap.php';
require_once __DIR__ . '/../functions.php';
require_once __DIR__ . '/../core/Services/SiroGeminiService.php';
require_once __DIR__ . '/../core/Services/FcmService.php';
try {
$con = Database::get('main');
// Fetch all reports from the last 7 days (that are not weekly themselves)
$stmt = $con->prepare("
SELECT platform, report_html, created_at
FROM marketing_reports
WHERE is_weekly = 0
AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
ORDER BY created_at ASC
");
$stmt->execute();
$reports = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($reports)) {
echo "No reports generated in the last 7 days to summarize.\n";
exit;
}
// Combine all report data for Gemini
$combinedData = [];
foreach ($reports as $report) {
$combinedData[] = [
'platform' => $report['platform'],
'date' => $report['created_at'],
'content' => strip_tags($report['report_html']) // Send stripped HTML to save tokens
];
}
$gemini = new SiroGeminiService();
// Create a new method in GeminiService or use evaluatePostsForReporting with a specific prompt wrapper
$prompt = "أنت خبير واستشاري تسويق الذكاء الاصطناعي (Chief Marketing Officer AI).
فيما يلي جميع التقارير اليومية والفردية لاستخبارات وسائل التواصل الاجتماعي التي جمعناها خلال الـ 7 أيام الماضية.
يرجى قراءتها بالكامل وتوليد 'ملخص استخبارات السوق الأسبوعي الشامل'.
قم بتنسيق التقرير بشكل جميل باستخدام HTML. يجب أن تبرز اتجاهات السوق الرئيسية، الشكاوى المتكررة، نشاطات المنافسين، ونصائح قابلة للتنفيذ لهذا الأسبوع.
هام جداً:
- يجب أن يكون التقرير باللغة العربية بالكامل.
- يجب أن تغلف كامل التقرير بـ <div dir=\"rtl\" align=\"right\"> في البداية و </div> في النهاية لضمان المحاذاة.
البيانات:
" . json_encode($combinedData, JSON_UNESCAPED_UNICODE);
$response = $gemini->callGemini($prompt, 'gemini-1.5-flash');
if (isset($response['candidates'][0]['content']['parts'][0]['text'])) {
$weeklyHtml = $response['candidates'][0]['content']['parts'][0]['text'];
// Save the weekly report
$insert = $con->prepare("INSERT INTO marketing_reports (platform, report_html, is_weekly) VALUES ('all', ?, 1)");
$insert->execute([$weeklyHtml]);
$reportId = $con->lastInsertId();
// Notify Admins
$fcm = new FcmService();
$fcm->send(
token: '/topics/admin_alerts',
title: 'Weekly Market Intelligence Summary 📈',
body: 'The comprehensive weekly social media report has been generated.',
data: [
'type' => 'marketing_report',
'report_id' => $reportId,
'is_weekly' => 1
],
category: 'marketing_report'
);
echo "Weekly report generated and saved successfully. ID: $reportId\n";
} else {
echo "Failed to generate report from Gemini.\n";
}
} catch (Exception $e) {
echo "Error generating weekly report: " . $e->getMessage() . "\n";
}
@@ -0,0 +1,118 @@
<?php
// ============================================================
// gemini_comment_generator.php
// Connects to Gemini API to generate natural, driver-like comments
// ============================================================
require_once __DIR__ . '/../core/bootstrap.php';
require_once __DIR__ . '/../functions.php';
// The API key should be stored in .env or the database. For now, reading from env.
$geminiApiKey = getenv('GEMINI_API_KEY');
if (!$geminiApiKey) {
die("Error: GEMINI_API_KEY not set in environment.\n");
}
function guessCountryFromContext($text) {
$text = mb_strtolower($text, 'UTF-8');
// Syrian Keywords
if (preg_match('/(يلاغو|يلا غو|yallago|yalla go|سوريا|دمشق|حلب|اللاذقية|ليرة|سيريتل|mtn|كازية|مازوت)/u', $text)) {
return 'SY';
}
// Egyptian Keywords
if (preg_match('/(ديدي|ان درايف|didi|indrive|مصر|القاهرة|جنيه|اسكندرية)/u', $text)) {
return 'EG';
}
// Jordanian Keywords (Default for Uber/Careem/Jeeny/TaxiF usually)
if (preg_match('/(جيني|jeeny|بترا|petra|عمان|اربد|دينار|تطبيقات ذكية|أوبر|كريم|uber|careem|تكسي اف|taxif)/u', $text)) {
return 'JO';
}
return 'SY'; // Fallback to Syria as requested by default
}
/**
* Generate a comment using Gemini API
*
* @param string $postContext The content of the post or the discussion
* @param string $intent What the bot should try to achieve (e.g. "promote_siro", "agree_and_empathize")
* @return string The generated comment
*/
function generateCommentWithGemini($postContext, $intent) {
global $geminiApiKey;
// Automatically guess the target country based on the keywords in the post
$country = guessCountryFromContext($postContext);
$url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-flash-lite-latest:generateContent?key=" . $geminiApiKey;
if ($country === 'SY') {
$systemInstruction = "أنت كابتن تطبيق توصيل أو راكب عادي في سوريا (دمشق، حلب، اللاذقية، طرطوس). تتحدث باللهجة السورية العامية المحكية (استخدم مصطلحات مثل: خيو، معلم، شلونك، والله، على راسي، شو القصة). تكتب بأسلوب عفوي جداً وشعبي، كأنك تكتب تعليقاً حقيقياً على فيسبوك وتختصر بالكلام. أنت تحب تطبيق 'سيرو' (Siro) لأنه حل أزمة الغلاء والتكلفة العالية في سوريا. لا تتحدث أبداً بأسلوب إعلاني، لا تستخدم العربية الفصحى، ولا تتحدث كمسوق لشركة. كن كشخص عادي يشارك تجربته مع الناس ويرد على أسئلتهم.";
} elseif ($country === 'EG') {
$systemInstruction = "أنت كابتن أو راكب في مصر (القاهرة، اسكندرية). تتحدث باللهجة المصرية العامية (يا باشا، يا اسطى، بصراحة، والله العظيم). أسلوبك شعبي وعفوي وطبيعي. ترى أن تطبيق 'سيرو' (Siro) أفضل وأوفر من ديدي وان درايف. لا تتحدث كمسوق.";
} elseif ($country === 'JO') {
$systemInstruction = "أنت كابتن تطبيق توصيل أو راكب في الأردن (عمان، إربد، الزرقاء). تتحدث باللهجة الأردنية العامية (يا زلمة، والله، لعاد، شو الأخبار). أسلوبك عفوي وعادي جداً. ترى أن تطبيق 'سيرو' (Siro) هو أفضل حل لأزمة المواصلات. لا تتحدث كمسوق.";
} else {
$systemInstruction = "أنت كابتن تطبيق توصيل تتحدث بلهجة محلية عامية. أسلوبك عفوي وطبيعي. تنصح الناس بتطبيق 'سيرو' (Siro). لا تتحدث كمسوق.";
}
$prompt = "السياق/المنشور الذي تقرأه الآن: \n\"" . $postContext . "\"\n\n";
if ($intent === 'promote_siro') {
$prompt .= "المطلوب: اكتب تعليقاً تتعاطف فيه مع المشكلة المذكورة (إن وجدت)، وانصح الزملاء بتجربة تطبيق 'سيرو' كحل واقعي وممتاز لكباتن النقل الذكي. اجعل التعليق قصيراً ومباشراً.";
} elseif ($intent === 'soft_promote_siro') {
$prompt .= "المطلوب: تفاعل مع المنشور بشكل طبيعي جداً وعفوي، شارك رأيك أو تعاطف مع المشكلة. قم بالتلميح بشكل خفيف جداً وعابر لتطبيق 'سيرو' كخيار واعد مستقبلاً دون أن تبدو كإعلان، لأن التطبيق لم يطلق رسمياً بعد. اجعله يبدو كحديث دردشة طبيعية.";
} elseif ($intent === 'answer_question') {
$prompt .= "المطلوب: أجب على السؤال المطروح في المنشور بوضوح بناءً على خبرتك مع تطبيق 'سيرو'.";
} elseif ($intent === 'dialogue_skeptic') {
$prompt .= "المطلوب: اكتب تعليقاً يبدي شكاً بسيطاً أو فضولاً كزبون أو راكب عادي (مثال: هل التطبيق آمن للبنات؟ أو هل الأسعار حقيقية فعلاً؟ أو كيف كباتنهم؟)، اجعل التعليق قصيراً وسؤالاً طبيعياً وعفوياً جداً بالعامية.";
} elseif ($intent === 'dialogue_supporter') {
$prompt .= "المطلوب: أجب على الشك المذكور أو أكد التجربة الإيجابية بحماس كراكب/كابتن آخر جرب تطبيق سيرو (Siro) فعلاً ووجد فيه الأمان والخصم الجيد والأسعار الثابتة، مع كتابته بلهجة عامية دافئة وطبيعية جداً.";
} else {
$prompt .= "المطلوب: اكتب تعليقاً متفاعلاً بشكل عام مع المنشور بأسلوب السائقين.";
}
$data = [
"contents" => [
[
"parts" => [
["text" => $systemInstruction . "\n\n" . $prompt]
]
]
],
"generationConfig" => [
"temperature" => 0.7, // A bit of creativity for varied responses
"maxOutputTokens" => 150
]
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
$result = json_decode($response, true);
if (isset($result['candidates'][0]['content']['parts'][0]['text'])) {
return trim($result['candidates'][0]['content']['parts'][0]['text']);
}
}
return "والله يا صاحبي جربت تطبيق سيرو وارتحت كثير من المشاكل هذي، جربه ما بتندم."; // Fallback response
}
// Example usage if called directly via CLI:
if (php_sapi_name() === 'cli') {
echo "Testing Gemini Generation...\n";
$testContext = "التطبيقات الثانية بتاخذ عمولة عالية جداً ومش ملحقين بنزين!";
$generated = generateCommentWithGemini($testContext, 'promote_siro');
echo "Generated Comment: \n" . $generated . "\n";
}
+130
View File
@@ -0,0 +1,130 @@
<?php
// ============================================================
// marketing_engine/index.php
// Main API endpoint for the Marketing Microservice
// ============================================================
require_once __DIR__ . '/../core/bootstrap.php';
require_once __DIR__ . '/../functions.php';
require_once __DIR__ . '/../core/Services/SiroGeminiService.php';
require_once __DIR__ . '/../core/Services/FcmService.php';
header('Content-Type: application/json');
// Simple bot authentication
$headers = getallheaders();
$botToken = $headers['X-Bot-Token'] ?? '';
// if ($botToken !== 'YOUR_SECRET_BOT_TOKEN') {
// http_response_code(401);
// exit(json_encode(['status' => 'error', 'message' => 'Unauthorized']));
// }
$action = $_GET['action'] ?? '';
$platform = $_GET['platform'] ?? 'facebook';
try {
$con = Database::get('main');
switch ($action) {
case 'get_task':
$stmt = $con->prepare("
SELECT id, type, target_url
FROM marketing_tasks
WHERE status = 'pending' AND platform = ?
ORDER BY id ASC LIMIT 1
");
$stmt->execute([$platform]);
$task = $stmt->fetch(PDO::FETCH_ASSOC);
if ($task) {
$update = $con->prepare("UPDATE marketing_tasks SET status = 'in_progress' WHERE id = ?");
$update->execute([$task['id']]);
echo json_encode(['status' => 'success', 'data' => $task]);
} else {
echo json_encode(['status' => 'success', 'message' => 'No tasks available', 'data' => null]);
}
break;
case 'complete_task':
$taskId = $_POST['task_id'] ?? null;
$result = $_POST['result'] ?? '';
if ($taskId) {
$stmt = $con->prepare("UPDATE marketing_tasks SET status = 'completed', completed_at = NOW() WHERE id = ?");
$stmt->execute([$taskId]);
echo json_encode(['status' => 'success']);
} else {
echo json_encode(['status' => 'error', 'message' => 'task_id required']);
}
break;
case 'fail_task':
$taskId = $_POST['task_id'] ?? null;
$errorMsg = $_POST['error_message'] ?? 'Unknown error';
if ($taskId) {
$stmt = $con->prepare("UPDATE marketing_tasks SET status = 'failed', error_message = ? WHERE id = ?");
$stmt->execute([$errorMsg, $taskId]);
echo json_encode(['status' => 'success']);
} else {
echo json_encode(['status' => 'error', 'message' => 'task_id required']);
}
break;
case 'evaluate_posts':
$input = json_decode(file_get_contents('php://input'), true);
$posts = $input['posts'] ?? [];
if (empty($posts)) {
echo json_encode(['status' => 'success', 'data' => null]);
break;
}
$gemini = new SiroGeminiService();
$reportHtml = $gemini->evaluatePostsForReporting($posts);
if ($reportHtml) {
// Save to database
$stmt = $con->prepare("INSERT INTO marketing_reports (platform, report_html) VALUES (?, ?)");
$stmt->execute([$platform, $reportHtml]);
$reportId = $con->lastInsertId();
// Send FCM notification to admins
$fcm = new FcmService();
$fcm->send(
token: '/topics/admin_alerts',
title: 'New Social Media Intelligence Report 📊',
body: "A new autonomous analysis report for {$platform} has been generated.",
data: [
'type' => 'marketing_report',
'report_id' => $reportId,
'platform' => $platform
],
category: 'marketing_report'
);
}
echo json_encode(['status' => 'success', 'message' => 'Posts evaluated and report saved']);
break;
case 'get_reports':
$stmt = $con->prepare("
SELECT id, platform, report_html, is_weekly, created_at
FROM marketing_reports
ORDER BY created_at DESC
LIMIT 50
");
$stmt->execute();
$reports = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['status' => 'success', 'data' => $reports]);
break;
default:
echo json_encode(['status' => 'error', 'message' => 'Invalid action']);
}
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => 'Server error: ' . $e->getMessage()]);
}
@@ -0,0 +1,18 @@
<?php
require_once __DIR__ . '/../core/bootstrap.php';
$con = Database::get('main');
$platform = $_GET['platform'] ?? 'facebook';
$targetUrl = $_GET['target_url'] ?? null;
if ($targetUrl) {
$stmt = $con->prepare("INSERT INTO marketing_tasks (platform, type, status, target_url) VALUES (?, 'autonomous_scroll_and_reply', 'pending', ?)");
$stmt->execute([$platform, $targetUrl]);
} else {
$stmt = $con->prepare("INSERT INTO marketing_tasks (platform, type, status) VALUES (?, 'autonomous_scroll_and_reply', 'pending')");
$stmt->execute([$platform]);
}
echo "<h1>Task inserted successfully!</h1>";
echo "<p>A new 'autonomous_scroll_and_reply' task is now pending for $platform.</p>";
echo "<p>Please restart the Android app now. It will immediately pick up this task and start scrolling!</p>";
@@ -0,0 +1,11 @@
<?php
require_once __DIR__ . '/../core/bootstrap.php';
$con = Database::get('main');
// Insert into marketing_tasks
$con->query("INSERT INTO marketing_tasks (platform, type, status) VALUES ('facebook', 'read_posts', 'pending')");
// Also insert into social_tasks just in case they hit the old endpoint
$con->query("INSERT INTO social_tasks (platform, type, status) VALUES ('facebook', 'read_posts', 'pending')");
echo "Test task inserted successfully!\n";
@@ -0,0 +1,123 @@
<?php
// ============================================================
// schedule_manager.php
// Creates and schedules tasks for the bots
// ============================================================
require_once __DIR__ . '/../core/bootstrap.php';
require_once __DIR__ . '/gemini_comment_generator.php';
class ScheduleManager {
private $con;
public function __construct() {
$this->con = Database::get('main');
}
/**
* Create a new task and optionally generate a comment for it using Gemini.
*
* @param string $platform 'facebook' or 'instagram'
* @param string $type 'join_group', 'read_posts', 'post_comment', 'share_link'
* @param string|null $targetUrl The group or post URL
* @param string|null $promptContext Context for Gemini
* @param int|null $accountId Specific account ID or null for any available
* @param int $delayMinutes How many minutes to delay this task
*/
public function scheduleTask($platform, $type, $targetUrl = null, $promptContext = null, $accountId = null, $delayMinutes = 0) {
// Quiet hours check (e.g. don't schedule tasks between 1 AM and 6 AM)
$currentHour = (int)date('H');
if ($currentHour >= 1 && $currentHour < 6) {
// Push it to 6 AM if we are in quiet hours
$hoursToAdd = 6 - $currentHour;
$delayMinutes += ($hoursToAdd * 60);
}
$scheduledAt = date('Y-m-d H:i:s', strtotime("+$delayMinutes minutes"));
$generatedComment = null;
if (($type === 'post_comment' || $type === 'share_link') && $promptContext) {
// Pre-generate the comment using Gemini
// If it's share_link, we want an intent to promote, otherwise just answer/interact
$intent = ($type === 'share_link') ? 'promote_siro' : 'answer_question';
$generatedComment = generateCommentWithGemini($promptContext, $intent);
}
$stmt = $this->con->prepare("
INSERT INTO social_tasks (account_id, platform, type, target_url, prompt_context, generated_comment, scheduled_at, status)
VALUES (?, ?, ?, ?, ?, ?, ?, 'pending')
");
$success = $stmt->execute([
$accountId,
$platform,
$type,
$targetUrl,
$promptContext,
$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]);
}
}
}
// Example usage via CLI
if (php_sapi_name() === 'cli' && isset($argv[1]) && $argv[1] === 'test_schedule') {
$sm = new ScheduleManager();
$context = "شو رأيكم بتطبيق سيرو يا كباتن؟ حد جربه؟";
$success = $sm->scheduleTask('facebook', 'post_comment', 'https://facebook.com/groups/amman.drivers/post/12345', $context, null, 5);
echo $success ? "Task scheduled successfully!\n" : "Failed to schedule task.\n";
}
@@ -0,0 +1,59 @@
<?php
// ============================================================
// marketing_engine/services/AIVideoGenerator.php
// Wrapper and Abstract Interfaces for AI Generation APIs
// ============================================================
class AIVideoGenerator {
private $elevenLabsApiKey;
private $creatomateApiKey;
private $geminiApiKey;
public function __construct() {
$this->elevenLabsApiKey = getenv('ELEVENLABS_API_KEY');
$this->creatomateApiKey = getenv('CREATOMATE_API_KEY');
$this->geminiApiKey = getenv('GEMINI_API_KEY');
}
// Abstracted text generation
public function generateScript($topic) {
// Here you would call Gemini API to generate a video script about the topic
// For example: "Write a 30 second TikTok script about how ride hailing drivers lose too much commission, and how Siro fixes this."
// Mock implementation
$prompt = "Write a short 20-word script about: " . $topic;
$mockScript = "Drivers are tired of high commissions. Siro is the solution with 0% commission forever. Join Siro today!";
return [
'status' => 'success',
'script' => $mockScript
];
}
// Abstracted voice generation
public function generateVoice($scriptText) {
// Here you would call ElevenLabs API (or similar) to convert text to speech
// It returns an MP3 file or URL
// Mock implementation
$mockAudioUrl = "https://example.com/audio_mock_".time().".mp3";
return [
'status' => 'success',
'audio_url' => $mockAudioUrl
];
}
// Abstracted video rendering
public function renderVideo($scriptText, $audioUrl, $backgroundVideoUrl = null) {
// Here you would call Creatomate or HeyGen to render the final MP4
// Combining the audio, background, and burning the captions on screen
// Mock implementation
$mockVideoUrl = "https://example.com/rendered_video_".time().".mp4";
return [
'status' => 'success',
'video_url' => $mockVideoUrl
];
}
}
@@ -0,0 +1,105 @@
<?php
// ============================================================
// marketing_engine/services/ContentWorkflow.php
// Orchestrates the entire content generation pipeline
// ============================================================
require_once __DIR__ . '/AIVideoGenerator.php';
require_once __DIR__ . '/../../core/bootstrap.php';
class ContentWorkflow {
private $generator;
private $con;
public function __construct() {
$this->generator = new AIVideoGenerator();
$this->con = Database::get('main');
}
/**
* Executes the next pending step in the content pipeline
*/
public function processPipeline() {
// 1. Check for pending topics to generate scripts
$this->processPendingScripts();
// 2. Check for generated scripts to create voice
$this->processPendingVoices();
// 3. Check for generated voices to render video
$this->processPendingVideos();
// 4. Check for rendered videos to queue marketing tasks
$this->queueUploadTasks();
}
private function processPendingScripts() {
$stmt = $this->con->prepare("SELECT * FROM content_pipeline WHERE status = 'pending' LIMIT 1");
$stmt->execute();
$job = $stmt->fetch(PDO::FETCH_ASSOC);
if ($job) {
$result = $this->generator->generateScript($job['topic']);
if ($result['status'] === 'success') {
$update = $this->con->prepare("UPDATE content_pipeline SET script_text = ?, status = 'script_generated' WHERE id = ?");
$update->execute([$result['script'], $job['id']]);
}
}
}
private function processPendingVoices() {
$stmt = $this->con->prepare("SELECT * FROM content_pipeline WHERE status = 'script_generated' LIMIT 1");
$stmt->execute();
$job = $stmt->fetch(PDO::FETCH_ASSOC);
if ($job) {
$result = $this->generator->generateVoice($job['script_text']);
if ($result['status'] === 'success') {
$update = $this->con->prepare("UPDATE content_pipeline SET voice_url = ?, status = 'voice_generated' WHERE id = ?");
$update->execute([$result['audio_url'], $job['id']]);
}
}
}
private function processPendingVideos() {
$stmt = $this->con->prepare("SELECT * FROM content_pipeline WHERE status = 'voice_generated' LIMIT 1");
$stmt->execute();
$job = $stmt->fetch(PDO::FETCH_ASSOC);
if ($job) {
$result = $this->generator->renderVideo($job['script_text'], $job['voice_url']);
if ($result['status'] === 'success') {
$update = $this->con->prepare("UPDATE content_pipeline SET video_url = ?, status = 'video_rendered' WHERE id = ?");
$update->execute([$result['video_url'], $job['id']]);
}
}
}
private function queueUploadTasks() {
$stmt = $this->con->prepare("SELECT * FROM content_pipeline WHERE status = 'video_rendered' LIMIT 1");
$stmt->execute();
$job = $stmt->fetch(PDO::FETCH_ASSOC);
if ($job) {
// Create upload tasks for TikTok and YouTube Shorts
$insert = $this->con->prepare("
INSERT INTO marketing_tasks (platform, type, content_text, media_url, status)
VALUES
('tiktok', 'upload_video', ?, ?, 'pending'),
('youtube', 'upload_video', ?, ?, 'pending')
");
// Provide a short caption based on the script
$caption = substr($job['script_text'], 0, 100) . "... #Siro #RideHailing";
$insert->execute([
$caption, $job['video_url'],
$caption, $job['video_url']
]);
// Mark job as published (or handed off to bots)
$update = $this->con->prepare("UPDATE content_pipeline SET status = 'published' WHERE id = ?");
$update->execute([$job['id']]);
}
}
}
+269
View File
@@ -0,0 +1,269 @@
<?php
// ============================================================
// social_worker.php
// Endpoint for the Android Social Media Bot
// ============================================================
require_once __DIR__ . '/../core/bootstrap.php';
require_once __DIR__ . '/../functions.php';
// Authentication and validation could be added here similar to driver_socket.php
// For now, let's keep it simple or use a static token for the bot
$headers = getallheaders();
$botToken = $headers['X-Bot-Token'] ?? '';
if ($botToken !== 'YOUR_SECRET_BOT_TOKEN') {
// In production, use a secure token or JWT
// http_response_code(401);
// exit(json_encode(['status' => 'error', 'message' => 'Unauthorized']));
}
$action = $_GET['action'] ?? '';
try {
$con = Database::get('main');
switch ($action) {
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
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'] ?: $requestedAccountId;
$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);
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
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']]);
$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]);
}
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':
// The bot reports task completion
$taskId = $_POST['task_id'] ?? null;
$result = $_POST['result'] ?? ''; // e.g. success or error details
if ($taskId) {
$stmt = $con->prepare("UPDATE social_tasks SET status = 'completed', completed_at = NOW() WHERE id = ?");
$stmt->execute([$taskId]);
// Log it
$log = $con->prepare("INSERT INTO social_logs (task_id, log_level, message) VALUES (?, 'info', ?)");
$log->execute([$taskId, "Task completed: " . substr($result, 0, 200)]);
echo json_encode(['status' => 'success']);
} else {
echo json_encode(['status' => 'error', 'message' => 'task_id required']);
}
break;
case 'fail_task':
// The bot reports task failure
$taskId = $_POST['task_id'] ?? null;
$errorMsg = $_POST['error_message'] ?? 'Unknown error';
if ($taskId) {
$stmt = $con->prepare("UPDATE social_tasks SET status = 'failed', error_message = ? WHERE id = ?");
$stmt->execute([$errorMsg, $taskId]);
// Log it
$log = $con->prepare("INSERT INTO social_logs (task_id, log_level, message) VALUES (?, 'error', ?)");
$log->execute([$taskId, "Task failed: " . substr($errorMsg, 0, 200)]);
echo json_encode(['status' => 'success']);
} else {
echo json_encode(['status' => 'error', 'message' => 'task_id required']);
}
break;
case 'log':
// The bot sends a general log
$accountId = $_POST['account_id'] ?? null;
$level = $_POST['level'] ?? 'info';
$message = $_POST['message'] ?? '';
$log = $con->prepare("INSERT INTO social_logs (account_id, log_level, message) VALUES (?, ?, ?)");
$log->execute([$accountId, $level, $message]);
echo json_encode(['status' => 'success']);
break;
default:
echo json_encode(['status' => 'error', 'message' => 'Invalid action']);
}
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => 'Database error: ' . $e->getMessage()]);
}
@@ -0,0 +1,157 @@
<?php
// telegram_scraper.php
if (php_sapi_name() !== 'cli') {
die("This script must be run from the command line (Terminal).\n");
}
// Load .env for CLI
$homeDir = '';
if (preg_match('#^(/home/[^/]+)#', __DIR__, $matches)) {
$homeDir = $matches[1];
}
$envPaths = [
__DIR__ . '/../.env',
__DIR__ . '/../../.env',
$homeDir ? $homeDir . '/.env' : ''
];
foreach ($envPaths as $envPath) {
if (file_exists($envPath)) {
$lines = file($envPath, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
$line = trim($line);
if (empty($line) || strpos($line, '#') === 0) continue;
$parts = explode('=', $line, 2);
if (count($parts) === 2) {
$key = trim($parts[0]);
$val = trim(trim($parts[1]), "\"'");
putenv("$key=$val");
$_ENV[$key] = $val;
}
}
break;
}
}
require_once __DIR__ . '/../core/Services/SiroGeminiService.php';
require_once __DIR__ . '/../core/Database/Database.php';
if (!file_exists(__DIR__ . '/madeline.php')) {
die("Error: madeline.php not found. Please run telegram_setup.php first.\n");
}
require_once __DIR__ . '/madeline.php';
if (!file_exists(__DIR__ . '/telegram_session.madeline')) {
die("Error: No session found. Please run telegram_setup.php to authenticate.\n");
}
echo "[*] Starting Telegram Scraper...\n";
$settings = new \danog\MadelineProto\Settings();
$settings->getAppInfo()->setApiId(2040);
$settings->getAppInfo()->setApiHash('b18441a1ff607e10a989891a5462e627');
$MadelineProto = new \danog\MadelineProto\API('telegram_session.madeline', $settings);
// Start MadelineProto (will resume session silently since it's already authenticated)
$MadelineProto->start();
// Define targets (Usernames, Group IDs, or Links)
// NOTE: You must be a member of the group/channel if it is private.
$targets = [
// 'https://t.me/example_group',
// '@example_channel',
'@YallaGo_Captains',
'@TaxiFJOR','@zakinntaxi','@zakinncaptains','@zakinaleppo','https://t.me/+rsfxbXEyDCczY2Nk'
];
if (empty($targets)) {
echo "[-] No targets defined. Please edit telegram_scraper.php and add your target groups/channels.\n";
exit;
}
$allMessages = [];
foreach ($targets as $target) {
echo "[*] Fetching latest messages from: $target\n";
try {
// Fetch history (last 50 messages per target)
$messages_Messages = $MadelineProto->messages->getHistory([
'peer' => $target,
'offset_id' => 0,
'offset_date' => 0,
'add_offset' => 0,
'limit' => 50,
'max_id' => 0,
'min_id' => 0,
'hash' => 0,
]);
if (isset($messages_Messages['messages'])) {
foreach ($messages_Messages['messages'] as $msg) {
if (isset($msg['message']) && !empty(trim($msg['message']))) {
// Prefix with [TELEGRAM] to inform Gemini
$allMessages[] = "[TELEGRAM]: " . trim($msg['message']);
}
}
}
} catch (\Exception $e) {
echo "[-] Error fetching $target: " . $e->getMessage() . "\n";
}
}
if (empty($allMessages)) {
echo "[-] No text messages found.\n";
exit;
}
echo "[+] Collected " . count($allMessages) . " messages.\n";
echo "--- Sample (First 3 messages) ---\n";
for($i=0; $i<min(3, count($allMessages)); $i++) {
echo $allMessages[$i] . "\n";
}
echo "---------------------------------\n";
echo "[*] Sending to Gemini AI for analysis...\n";
$gemini = new SiroGeminiService();
if (!getenv('GEMINI_API_KEY') && empty($_ENV['GEMINI_API_KEY'])) {
echo "[-] ERROR: GEMINI_API_KEY is completely missing in CLI environment. Make sure your .env file exists.\n";
exit;
}
$reportHtml = $gemini->evaluateTelegramForReporting($allMessages);
if ($reportHtml && strpos($reportHtml, 'لا توجد بيانات مفيدة') === false) {
echo "[+] AI generated a useful report. Saving to DB...\n";
$con = Database::get('main');
$stmt = $con->prepare("INSERT INTO marketing_reports (platform, report_html) VALUES (?, ?)");
$stmt->execute(['telegram', $reportHtml]);
$reportId = $con->lastInsertId();
echo "[+] Report saved with ID: $reportId\n";
// Attempt FCM Alert
try {
require_once __DIR__ . '/../core/Services/FcmService.php';
$fcm = new FcmService();
$fcm->send(
token: '/topics/admin_alerts',
title: 'New Telegram Intelligence Report 📊',
body: "A new autonomous analysis report for Telegram has been generated.",
data: ['type' => 'marketing_report', 'report_id' => (string)$reportId]
);
echo "[+] Sent FCM alert to admins.\n";
} catch (\Exception $e) {
echo "[-] Error sending FCM: " . $e->getMessage() . "\n";
}
} else {
echo "[-] AI Report: No useful data or API Error.\n";
echo "--- Gemini Raw Output ---\n";
echo var_export($reportHtml, true) . "\n";
echo "-------------------------\n";
}
echo "[*] Done.\n";
@@ -0,0 +1,30 @@
<?php
if (php_sapi_name() !== 'cli') {
die("This script must be run from the command line (Terminal).\n");
}
echo "=================================================\n";
echo " Siro Telegram MTProto Setup (MadelineProto) \n";
echo "=================================================\n\n";
if (!file_exists('madeline.php')) {
echo "[*] Downloading MadelineProto Library... (This may take a moment)\n";
copy('https://phar.madelineproto.xyz/madeline.php', 'madeline.php');
echo "[+] Download complete.\n";
}
require_once 'madeline.php';
echo "[*] Initializing Telegram Client...\n";
echo "[!] You will be prompted to enter your phone number (with country code, e.g. +9627...)\n";
echo "[!] Then, enter the OTP code sent to your Telegram app.\n\n";
$settings = new \danog\MadelineProto\Settings();
$settings->getAppInfo()->setApiId(2040);
$settings->getAppInfo()->setApiHash('b18441a1ff607e10a989891a5462e627');
$MadelineProto = new \danog\MadelineProto\API('telegram_session.madeline', $settings);
$MadelineProto->start();
echo "\n✅ Successfully authenticated! Session saved to telegram_session.madeline\n";
echo "✅ You can now use telegram_scraper.php to fetch messages autonomously.\n";
@@ -0,0 +1,113 @@
<?php
// ==============================================================================
# Siro Guerrilla Marketing Flow Verification & Integration Test Script
# ==============================================================================
# This script executes a full dry-run integration test on the marketing engine.
# It checks proxy assignments, country detection, dialect generation, and
# dialogue scheduling.
# ==============================================================================
require_once __DIR__ . '/../core/bootstrap.php';
require_once __DIR__ . '/schedule_manager.php';
require_once __DIR__ . '/account_manager.php';
try {
$con = Database::get('main');
} catch (Exception $e) {
die("[-] Connection failed: " . $e->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";