Update: 2026-07-04 20:53:30
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,88 @@
|
||||
<?php
|
||||
// ============================================================
|
||||
// marketing_engine/index.php
|
||||
// Main API endpoint for the Marketing Microservice
|
||||
// ============================================================
|
||||
|
||||
require_once __DIR__ . '/../core/bootstrap.php';
|
||||
require_once __DIR__ . '/../functions.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'] ?? 'tiktok'; // Default to tiktok for new platform testing
|
||||
|
||||
try {
|
||||
// In a real scenario, you'd use a dedicated database.
|
||||
// Here we use the main DB but with marketing_ prefixed tables.
|
||||
$con = Database::get('main');
|
||||
|
||||
switch ($action) {
|
||||
case 'get_task':
|
||||
// 1. First, check if the bot needs a task
|
||||
// We look for pending tasks for this platform
|
||||
$stmt = $con->prepare("
|
||||
SELECT id, type, target_url, content_text, media_url
|
||||
FROM marketing_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) {
|
||||
// If the task requires a media file (like upload_video)
|
||||
// The Android app will need to download it first using the media_url
|
||||
|
||||
// Mark as in progress
|
||||
$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;
|
||||
|
||||
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,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,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,70 @@
|
||||
-- ==========================================================
|
||||
-- Marketing Engine & Social Media Bot Combined Schema
|
||||
-- ==========================================================
|
||||
|
||||
-- 1. Original Social Media Tables (Facebook, Instagram)
|
||||
CREATE TABLE IF NOT EXISTS `social_accounts` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`platform` ENUM('facebook', 'instagram', 'tiktok', 'twitter', 'youtube') NOT NULL,
|
||||
`username` VARCHAR(100) NOT NULL,
|
||||
`status` ENUM('active', 'restricted', 'banned') DEFAULT 'active',
|
||||
`total_posts` INT DEFAULT 0,
|
||||
`total_comments` INT DEFAULT 0,
|
||||
`total_videos` 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,
|
||||
`platform` ENUM('facebook', 'instagram', 'tiktok', 'twitter', 'youtube') NOT NULL,
|
||||
`type` ENUM('join_group', 'read_posts', 'post_comment', 'share_link', 'upload_video', 'post_tweet') NOT NULL,
|
||||
`target_url` VARCHAR(500) NULL, -- URL of the group, post, or media download
|
||||
`prompt_context` TEXT NULL,
|
||||
`generated_comment` TEXT NULL,
|
||||
`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
|
||||
);
|
||||
|
||||
-- 2. New Content Generation Pipeline Tables
|
||||
CREATE TABLE IF NOT EXISTS `content_pipeline` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`topic` VARCHAR(255) NOT NULL,
|
||||
`script_text` TEXT NULL,
|
||||
`voice_url` VARCHAR(500) NULL,
|
||||
`video_url` VARCHAR(500) NULL,
|
||||
`status` ENUM('pending', 'script_generated', 'voice_generated', 'video_rendered', 'published', 'failed') DEFAULT 'pending',
|
||||
`error_message` TEXT NULL,
|
||||
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `api_quotas` (
|
||||
`id` INT AUTO_INCREMENT PRIMARY KEY,
|
||||
`service_name` ENUM('gemini', 'elevenlabs', 'creatomate', 'heygen') NOT NULL,
|
||||
`daily_usage` INT DEFAULT 0,
|
||||
`quota_limit` INT DEFAULT 10,
|
||||
`last_reset` DATE NOT NULL
|
||||
);
|
||||
|
||||
INSERT IGNORE INTO `api_quotas` (`service_name`, `daily_usage`, `quota_limit`, `last_reset`) VALUES
|
||||
('gemini', 0, 100, CURDATE()),
|
||||
('elevenlabs', 0, 5, CURDATE()),
|
||||
('creatomate', 0, 2, CURDATE());
|
||||
@@ -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']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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