Update: 2026-07-05 00:21:29

This commit is contained in:
Hamza-Ayed
2026-07-05 00:21:29 +03:00
parent 35c0a6680c
commit 5cf809e0cb
11 changed files with 418 additions and 9 deletions
+6 -2
View File
@@ -266,8 +266,12 @@ class SiroGeminiService {
2. تلخيص أهم هذه النقاشات في تقرير قصير ومفيد (News Report).
3. تجاهل المنشورات العشوائية أو الشخصية التي لا تفيد السوق.
قم بصياغة تقريرك بتنسيق HTML مرتب وجاهز للعرض (بدون علامات ```html)، واستخدم <h3> للعناوين و <ul> للقوائم.
إذا لم يكن هناك أي شيء مفيد، اكتب فقط: <p>لا توجد بيانات مفيدة في هذه الدفعة.</p>
هام جداً:
- يجب أن يكون التقرير باللغة العربية بالكامل.
- يجب أن تغلف التقرير بالكامل بوسم <div dir=\"rtl\" align=\"right\"> في البداية و </div> في النهاية لضمان القراءة من اليمين لليسار.
- استخدم <h3> للعناوين و <ul> للقوائم داخل التقرير.
- لا تقم بتضمين علامات ```html في المخرجات.
إذا لم يكن هناك أي شيء مفيد، اكتب فقط: <div dir=\"rtl\" align=\"right\"><p>لا توجد بيانات مفيدة في هذه الدفعة.</p></div>
";
$url = $this->baseUrl . "{$model}:generateContent?key={$this->apiKey}";
@@ -41,7 +41,17 @@ try {
$gemini = new SiroGeminiService();
// Create a new method in GeminiService or use evaluatePostsForReporting with a specific prompt wrapper
$prompt = "You are a Chief Marketing Officer AI. Below are all the daily/individual social media intelligence reports collected over the last 7 days from our scraper bots. Please read through all of them and generate one comprehensive, executive 'Weekly Market Intelligence Summary'. Format it beautifully in HTML. Highlight key market trends, recurrent complaints, competitor activities, and actionable advice for the week.\n\nData:\n" . json_encode($combinedData);
$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');
+1 -1
View File
@@ -28,7 +28,7 @@ try {
switch ($action) {
case 'get_task':
$stmt = $con->prepare("
SELECT id, type
SELECT id, type, target_url, prompt_context
FROM marketing_tasks
WHERE status = 'pending' AND platform = ?
ORDER BY id ASC LIMIT 1
@@ -2,9 +2,17 @@
require_once __DIR__ . '/../core/bootstrap.php';
$con = Database::get('main');
$stmt = $con->prepare("INSERT INTO marketing_tasks (platform, type, status) VALUES ('facebook', 'autonomous_scroll_and_reply', 'pending')");
$stmt->execute();
$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 Facebook.</p>";
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>";
@@ -13,6 +13,7 @@
<package android:name="com.instagram.android" />
<package android:name="com.twitter.android" />
<package android:name="com.zhiliaoapp.musically" />
<package android:name="org.telegram.messenger" />
</queries>
<application
@@ -63,6 +64,20 @@
android:resource="@xml/instagram_accessibility_service_config" />
</service>
<!-- Telegram Accessibility Service -->
<service
android:name=".social.telegram.TelegramBotService"
android:label="Siro Telegram Bot"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/telegram_accessibility_service_config" />
</service>
</application>
</manifest>
@@ -54,9 +54,9 @@ object SocialBotClient {
postData("action=complete_task", "task_id=$taskId&result=$result")
}
fun evaluatePosts(posts: List<String>): JSONObject? {
fun evaluatePosts(posts: List<String>, platform: String = "facebook"): JSONObject? {
try {
val urlString = "$BASE_URL?action=evaluate_posts"
val urlString = "$BASE_URL?action=evaluate_posts&platform=$platform"
val url = URL(urlString)
val connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "POST"
@@ -87,6 +87,59 @@ class InstagramBotService : AccessibilityService() {
}
}
}
"autonomous_scroll_and_reply" -> {
Log.d(TAG, "Starting autonomous mode for Instagram...")
if (task.targetUrl != null) {
navigator.openUrl(task.targetUrl)
} else {
navigator.openApp()
}
delay(3000)
val allCollectedTexts = mutableSetOf<String>()
val totalIterations = 15 // Roughly 3-4 minutes of scrolling
var iterations = 0
var consecutiveNoNewPosts = 0
while (iterations < totalIterations) {
Log.d(TAG, "Autonomous Iteration: ${iterations + 1} of $totalIterations")
val initialSize = allCollectedTexts.size
// 1. Read feed posts and comments on screen
val posts = navigator.extractPostsAndComments()
// Add to our global collection (Set avoids duplicates)
allCollectedTexts.addAll(posts)
Log.d(TAG, "Collected ${posts.size} texts in this iteration. Total unique so far: ${allCollectedTexts.size}")
if (allCollectedTexts.size == initialSize) {
consecutiveNoNewPosts++
Log.d(TAG, "No new posts found in this iteration. (Streak: $consecutiveNoNewPosts)")
if (consecutiveNoNewPosts >= 3) {
Log.d(TAG, "No new posts for 3 consecutive iterations. Reached bottom or stuck. Breaking early.")
break
}
} else {
consecutiveNoNewPosts = 0
}
Log.d(TAG, "Scrolling down for more posts...")
navigator.scrollForward()
delay(3000) // wait for new posts to load
iterations++
}
// Send everything to Gemini AT ONCE to save API costs
if (allCollectedTexts.isNotEmpty()) {
Log.d(TAG, "Finished scrolling. Sending ${allCollectedTexts.size} texts to backend (platform=instagram)...")
// The endpoint should handle this dynamically if we pass platform to SocialBotClient
// Wait, SocialBotClient currently uses a hardcoded url or passes platform?
// Let's assume evaluatePosts can take platform or defaults to facebook. We will need to update evaluatePosts in SocialBotClient.
SocialBotClient.evaluatePosts(allCollectedTexts.toList(), "instagram")
}
SocialBotClient.completeTask(task.id, "Autonomous session completed. Scraped ${allCollectedTexts.size} items.")
}
else -> {
Log.w(TAG, "Unknown task type: ${task.type}")
SocialBotClient.failTask(task.id, "Unknown task type")
@@ -27,6 +27,17 @@ class InstagramNavigator(private val service: AccessibilityService) {
delay(4_000) // Wait for Instagram to load
}
suspend fun openApp() {
val intent = service.packageManager.getLaunchIntentForPackage("com.instagram.android")
if (intent != null) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
service.startActivity(intent)
delay(5_000)
} else {
// Instagram not installed
}
}
suspend fun postComment(commentText: String): Boolean {
val root = service.rootInActiveWindow ?: return false
@@ -56,6 +67,71 @@ class InstagramNavigator(private val service: AccessibilityService) {
// ─── Utility helpers ───────────────────────────────────────────────────────
fun scrollForward(): Boolean {
val root = service.rootInActiveWindow ?: return false
val listNode = findScrollableNode(root)
if (listNode != null) {
val scrolled = listNode.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)
if (scrolled) return true
}
// Fallback to gesture swipe
val displayMetrics = service.resources.displayMetrics
val width = displayMetrics.widthPixels
val height = displayMetrics.heightPixels
val path = android.graphics.Path()
path.moveTo(width / 2f, height * 0.8f)
path.lineTo(width / 2f, height * 0.2f)
val gesture = android.accessibilityservice.GestureDescription.Builder()
.addStroke(android.accessibilityservice.GestureDescription.StrokeDescription(path, 0, 500))
.build()
return service.dispatchGesture(gesture, null, null)
}
fun extractPostsAndComments(): List<String> {
val root = service.rootInActiveWindow ?: return emptyList()
val extractedTexts = mutableSetOf<String>()
extractTextRecursively(root, extractedTexts)
return extractedTexts.toList()
}
private fun extractTextRecursively(node: AccessibilityNodeInfo?, results: MutableSet<String>) {
if (node == null) return
// Only target TextViews
if (node.className?.toString() == "android.widget.TextView") {
val text = node.text?.toString()?.trim()
val contentDesc = node.contentDescription?.toString()?.trim()
val validText = text ?: contentDesc
// Filter out short UI elements, timestamps, likes, etc.
if (!validText.isNullOrEmpty() && validText.length > 20 && !validText.contains(Regex("^[0-9]+ likes$"))) {
results.add(validText)
}
}
for (i in 0 until node.childCount) {
extractTextRecursively(node.getChild(i), results)
}
}
private fun findScrollableNode(node: AccessibilityNodeInfo?): AccessibilityNodeInfo? {
if (node == null) return null
if (node.isScrollable && (node.className?.toString()?.contains("RecyclerView") == true || node.className?.toString()?.contains("ListView") == true)) {
return node
}
for (i in 0 until node.childCount) {
val found = findScrollableNode(node.getChild(i))
if (found != null) return found
}
return null
}
private fun findNodeByHint(node: AccessibilityNodeInfo?, hint: String): AccessibilityNodeInfo? {
if (node == null) return null
if (node.hintText?.toString()?.contains(hint, ignoreCase = true) == true) return node
@@ -0,0 +1,128 @@
package com.siro.socialmedia_bot.social.telegram
import android.accessibilityservice.AccessibilityService
import android.view.accessibility.AccessibilityEvent
import android.util.Log
import com.siro.socialmedia_bot.network.SocialBotClient
import com.siro.socialmedia_bot.social.model.SocialTask
import kotlinx.coroutines.*
class TelegramBotService : AccessibilityService() {
private val TAG = "TelegramBotService"
private val scope = CoroutineScope(Dispatchers.IO + Job())
private var isBotRunning = false
private lateinit var navigator: TelegramNavigator
override fun onServiceConnected() {
super.onServiceConnected()
Log.d(TAG, "Telegram Accessibility Service Connected")
navigator = TelegramNavigator(this)
startTaskLoop()
}
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
// Observe events when needed for synchronization
}
override fun onInterrupt() {
Log.d(TAG, "Service Interrupted")
isBotRunning = false
}
override fun onDestroy() {
super.onDestroy()
scope.cancel()
}
private fun startTaskLoop() {
if (isBotRunning) return
isBotRunning = true
scope.launch {
while (isBotRunning) {
try {
val taskData = SocialBotClient.getTask("telegram")
if (taskData != null) {
val task = SocialTask(
id = taskData.getInt("id"),
type = taskData.getString("type"),
targetUrl = if (taskData.isNull("target_url")) null else taskData.getString("target_url"),
promptContext = if (taskData.isNull("prompt_context")) null else taskData.getString("prompt_context"),
generatedComment = if (taskData.isNull("generated_comment")) null else taskData.getString("generated_comment")
)
Log.d(TAG, "Received Task: ${task.type}")
executeTask(task)
} else {
Log.d(TAG, "No Telegram tasks. Sleeping...")
delay(60_000)
}
} catch (e: Exception) {
Log.e(TAG, "Error in task loop", e)
delay(30_000)
}
}
}
}
private suspend fun executeTask(task: SocialTask) {
try {
when (task.type) {
"autonomous_scroll_and_reply" -> {
Log.d(TAG, "Starting autonomous mode for Telegram...")
if (task.targetUrl != null) {
navigator.openChannel(task.targetUrl)
} else {
navigator.openApp()
}
delay(3000)
val allCollectedTexts = mutableSetOf<String>()
val totalIterations = 15
var iterations = 0
var consecutiveNoNewPosts = 0
while (iterations < totalIterations) {
Log.d(TAG, "Autonomous Iteration: ${iterations + 1} of $totalIterations")
val initialSize = allCollectedTexts.size
val posts = navigator.extractPostsAndComments()
allCollectedTexts.addAll(posts)
Log.d(TAG, "Collected ${posts.size} texts in this iteration. Total unique so far: ${allCollectedTexts.size}")
if (allCollectedTexts.size == initialSize) {
consecutiveNoNewPosts++
Log.d(TAG, "No new posts found in this iteration. (Streak: $consecutiveNoNewPosts)")
if (consecutiveNoNewPosts >= 3) {
break
}
} else {
consecutiveNoNewPosts = 0
}
Log.d(TAG, "Scrolling up for more posts...")
navigator.scrollForward()
delay(2500)
iterations++
}
if (allCollectedTexts.isNotEmpty()) {
Log.d(TAG, "Finished scrolling. Sending ${allCollectedTexts.size} texts to backend (platform=telegram)...")
SocialBotClient.evaluatePosts(allCollectedTexts.toList(), "telegram")
}
SocialBotClient.completeTask(task.id, "Autonomous session completed. Scraped ${allCollectedTexts.size} items.")
}
else -> {
Log.w(TAG, "Unknown task type: ${task.type}")
SocialBotClient.failTask(task.id, "Unknown task type")
}
}
} catch (e: Exception) {
SocialBotClient.failTask(task.id, "Exception: ${e.message}")
}
delay((15_000..30_000).random().toLong())
}
}
@@ -0,0 +1,105 @@
package com.siro.socialmedia_bot.social.telegram
import android.accessibilityservice.AccessibilityService
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.accessibility.AccessibilityNodeInfo
import kotlinx.coroutines.delay
/**
* Handles navigation and interaction within the Telegram app.
* Telegram's package name: org.telegram.messenger
*/
class TelegramNavigator(private val service: AccessibilityService) {
suspend fun openApp() {
val intent = service.packageManager.getLaunchIntentForPackage("org.telegram.messenger")
if (intent != null) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
service.startActivity(intent)
delay(5_000)
} else {
// Telegram not installed
}
}
suspend fun openChannel(channelLink: String) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(channelLink))
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.setPackage("org.telegram.messenger")
try {
service.startActivity(intent)
} catch (e: Exception) {
intent.setPackage(null)
service.startActivity(intent)
}
delay(4_000)
}
fun scrollForward(): Boolean {
val root = service.rootInActiveWindow ?: return false
val listNode = findScrollableNode(root)
if (listNode != null) {
val scrolled = listNode.performAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD)
// Telegram might scroll backward if it's reading top to bottom in chat, but we usually want older posts
// so scroll backward is actually ACTION_SCROLL_BACKWARD for loading old messages.
if (scrolled) return true
}
val displayMetrics = service.resources.displayMetrics
val width = displayMetrics.widthPixels
val height = displayMetrics.heightPixels
val path = android.graphics.Path()
path.moveTo(width / 2f, height * 0.2f) // swipe down to go up in chat
path.lineTo(width / 2f, height * 0.8f)
val gesture = android.accessibilityservice.GestureDescription.Builder()
.addStroke(android.accessibilityservice.GestureDescription.StrokeDescription(path, 0, 500))
.build()
return service.dispatchGesture(gesture, null, null)
}
fun extractPostsAndComments(): List<String> {
val root = service.rootInActiveWindow ?: return emptyList()
val extractedTexts = mutableSetOf<String>()
extractTextRecursively(root, extractedTexts)
return extractedTexts.toList()
}
private fun extractTextRecursively(node: AccessibilityNodeInfo?, results: MutableSet<String>) {
if (node == null) return
// Target TextViews
if (node.className?.toString() == "android.widget.TextView") {
val text = node.text?.toString()?.trim()
val contentDesc = node.contentDescription?.toString()?.trim()
val validText = text ?: contentDesc
// Filter short words
if (!validText.isNullOrEmpty() && validText.length > 20) {
results.add(validText)
}
}
for (i in 0 until node.childCount) {
extractTextRecursively(node.getChild(i), results)
}
}
private fun findScrollableNode(node: AccessibilityNodeInfo?): AccessibilityNodeInfo? {
if (node == null) return null
if (node.isScrollable && (node.className?.toString()?.contains("RecyclerView") == true || node.className?.toString()?.contains("ListView") == true || node.className?.toString()?.contains("ScrollView") == true)) {
return node
}
for (i in 0 until node.childCount) {
val found = findScrollableNode(node.getChild(i))
if (found != null) return found
}
return null
}
}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagIncludeNotImportantViews|flagReportViewIds"
android:canRetrieveWindowContent="true"
android:canPerformGestures="true"
android:description="@string/accessibility_service_description"
android:notificationTimeout="100"
android:packageNames="org.telegram.messenger" />