Update: 2026-07-04 18:43:56
This commit is contained in:
@@ -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
|
||||
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,81 @@
|
||||
<?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");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
$url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=" . $geminiApiKey;
|
||||
|
||||
$systemInstruction = "أنت سائق سيارة أجرة في الأردن أو السعودية. تتحدث بلهجة عامية بسيطة جداً، وتكتب أحياناً بأسلوب عفوي (ممكن مع أخطاء مطبعية بسيطة لتكون طبيعياً). أنت تستخدم تطبيق 'سيرو' (Siro) للنقل الذكي وتراه التطبيق الأفضل لأنه يوفر دخل ممتاز ولا يظلم السائق. لا تتحدث كمسوق لشركة، بل كزميل ناصح.";
|
||||
|
||||
$prompt = "السياق/المنشور الذي تقرأه الآن: \n\"" . $postContext . "\"\n\n";
|
||||
|
||||
if ($intent === 'promote_siro') {
|
||||
$prompt .= "المطلوب: اكتب تعليقاً تتعاطف فيه مع المشكلة المذكورة (إن وجدت)، وانصح الزملاء بتجربة تطبيق 'سيرو' كحل واقعي وممتاز لكباتن النقل الذكي. اجعل التعليق قصيراً ومباشراً.";
|
||||
} elseif ($intent === 'answer_question') {
|
||||
$prompt .= "المطلوب: أجب على السؤال المطروح في المنشور بوضوح بناءً على خبرتك مع تطبيق 'سيرو'.";
|
||||
} 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";
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?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')
|
||||
");
|
||||
|
||||
return $stmt->execute([
|
||||
$accountId,
|
||||
$platform,
|
||||
$type,
|
||||
$targetUrl,
|
||||
$promptContext,
|
||||
$generatedComment,
|
||||
$scheduledAt
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// 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,43 @@
|
||||
-- Social Media Bot Schema
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `social_accounts` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`platform` ENUM('facebook', 'instagram') NOT NULL,
|
||||
`username` VARCHAR(100) NOT NULL,
|
||||
`status` ENUM('active', 'restricted', 'banned') DEFAULT 'active',
|
||||
`total_posts` INT DEFAULT 0,
|
||||
`total_comments` INT DEFAULT 0,
|
||||
`last_active` DATETIME NULL,
|
||||
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `social_tasks` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`account_id` INT NULL, -- NULL if any available account can take it
|
||||
`platform` ENUM('facebook', 'instagram') NOT NULL,
|
||||
`type` ENUM('join_group', 'read_posts', 'post_comment', 'share_link') NOT NULL,
|
||||
`target_url` VARCHAR(500) NULL, -- URL of the group or post
|
||||
`prompt_context` TEXT NULL, -- Context for Gemini to generate the comment
|
||||
`generated_comment` TEXT NULL, -- The comment generated by Gemini
|
||||
`status` ENUM('pending', 'in_progress', 'completed', 'failed') DEFAULT 'pending',
|
||||
`error_message` TEXT NULL,
|
||||
`scheduled_at` DATETIME NULL,
|
||||
`completed_at` DATETIME NULL,
|
||||
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`account_id`) REFERENCES `social_accounts`(`id`) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `social_logs` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`task_id` INT NULL,
|
||||
`account_id` INT NULL,
|
||||
`log_level` ENUM('info', 'warning', 'error') DEFAULT 'info',
|
||||
`message` TEXT NOT NULL,
|
||||
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (`task_id`) REFERENCES `social_tasks`(`id`) ON DELETE SET NULL,
|
||||
FOREIGN KEY (`account_id`) REFERENCES `social_accounts`(`id`) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
-- Insert dummy account for testing
|
||||
INSERT IGNORE INTO `social_accounts` (`platform`, `username`, `status`) VALUES ('facebook', 'test_bot_1', 'active');
|
||||
@@ -0,0 +1,108 @@
|
||||
<?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';
|
||||
|
||||
// Find a pending task that is scheduled for now or earlier
|
||||
$stmt = $con->prepare("
|
||||
SELECT 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) {
|
||||
// Mark as in progress
|
||||
$update = $con->prepare("UPDATE social_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':
|
||||
// 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()]);
|
||||
}
|
||||
Reference in New Issue
Block a user