Update: 2026-07-05 01:32:30

This commit is contained in:
Hamza-Ayed
2026-07-05 01:32:30 +03:00
parent 468b06ffb3
commit 73d4778dab
6 changed files with 139 additions and 13 deletions
@@ -2,8 +2,12 @@
require_once __DIR__ . '/../core/bootstrap.php';
$con = Database::get('main');
// Insert the autonomous_scroll_and_reply task
$stmt = $con->prepare("INSERT INTO marketing_tasks (platform, type, status) VALUES ('facebook', 'autonomous_scroll_and_reply', 'pending')");
$stmt->execute();
// Insert the autonomous_scroll_and_reply task for each platform
$platforms = ['facebook', 'instagram'];
echo "Task inserted successfully at " . date('Y-m-d H:i:s') . "\n";
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,51 @@
package com.siro.socialmedia_bot.data
import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import java.security.MessageDigest
class SeenPostDatabase(context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
companion object {
private const val DATABASE_NAME = "SeenPostsDB"
private const val DATABASE_VERSION = 1
private const val TABLE_SEEN_POSTS = "seen_posts"
private const val COLUMN_HASH = "hash"
}
override fun onCreate(db: SQLiteDatabase) {
val createTable = "CREATE TABLE $TABLE_SEEN_POSTS ($COLUMN_HASH TEXT PRIMARY KEY)"
db.execSQL(createTable)
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("DROP TABLE IF EXISTS $TABLE_SEEN_POSTS")
onCreate(db)
}
fun isPostSeen(text: String): Boolean {
val hash = generateHash(text)
val db = this.readableDatabase
val cursor = db.rawQuery("SELECT 1 FROM $TABLE_SEEN_POSTS WHERE $COLUMN_HASH = ?", arrayOf(hash))
val exists = cursor.count > 0
cursor.close()
return exists
}
fun markPostAsSeen(text: String) {
val hash = generateHash(text)
val db = this.writableDatabase
val values = ContentValues().apply {
put(COLUMN_HASH, hash)
}
db.insertWithOnConflict(TABLE_SEEN_POSTS, null, values, SQLiteDatabase.CONFLICT_IGNORE)
}
private fun generateHash(input: String): String {
val md = MessageDigest.getInstance("SHA-256")
val digest = md.digest(input.toByteArray())
return digest.fold("") { str, it -> str + "%02x".format(it) }
}
}
@@ -3,8 +3,12 @@ package com.siro.socialmedia_bot.social.facebook
import android.accessibilityservice.AccessibilityService
import android.view.accessibility.AccessibilityNodeInfo
import com.siro.socialmedia_bot.data.SeenPostDatabase
class FacebookCommentReader(private val service: AccessibilityService) {
private val seenDb = SeenPostDatabase(service)
fun extractPostsAndComments(): List<String> {
val root = service.rootInActiveWindow ?: return emptyList()
val extractedTexts = mutableListOf<String>()
@@ -32,8 +36,12 @@ class FacebookCommentReader(private val service: AccessibilityService) {
val text = node.text?.toString() ?: node.contentDescription?.toString()
if (!text.isNullOrBlank()) {
if (isValidPostText(text)) {
collectedTexts.add(text.trim())
val trimmedText = text.trim()
if (isValidPostText(trimmedText)) {
if (!seenDb.isPostSeen(trimmedText)) {
collectedTexts.add(trimmedText)
seenDb.markPostAsSeen(trimmedText)
}
}
}
@@ -110,13 +110,25 @@ class InstagramBotService : AccessibilityService() {
// Add to our global collection (Set avoids duplicates)
allCollectedTexts.addAll(posts)
if (navigator.openComments()) {
Log.d(TAG, "Opened comments section...")
delay(3000)
val comments = navigator.extractPostsAndComments()
allCollectedTexts.addAll(comments)
// Go back to the feed
performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK)
delay(2000)
}
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.")
if (consecutiveNoNewPosts >= 5) {
Log.d(TAG, "No new posts for 5 consecutive iterations. Reached bottom or stuck. Breaking early.")
break
}
} else {
@@ -7,12 +7,16 @@ import android.os.Bundle
import android.view.accessibility.AccessibilityNodeInfo
import kotlinx.coroutines.delay
import com.siro.socialmedia_bot.data.SeenPostDatabase
/**
* Handles navigation and interaction within the Instagram app.
* Instagram's package name: com.instagram.android
*/
class InstagramNavigator(private val service: AccessibilityService) {
private val seenDb = SeenPostDatabase(service)
suspend fun openUrl(url: String) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -110,9 +114,12 @@ class InstagramNavigator(private val service: AccessibilityService) {
val validText = text ?: contentDesc
// Filter out short UI elements, timestamps, likes, etc.
if (!validText.isNullOrEmpty() && validText.length > 20 && !validText.contains(Regex("^[0-9]+ likes$"))) {
if (!validText.isNullOrEmpty() && validText.length >= 10 && !validText.contains(Regex("^[0-9]+ likes$"))) {
if (!seenDb.isPostSeen(validText)) {
android.util.Log.d("ScrapedText", "Extracted: $validText")
results.add(validText)
seenDb.markPostAsSeen(validText)
}
}
}
@@ -133,6 +140,43 @@ class InstagramNavigator(private val service: AccessibilityService) {
return null
}
fun openComments(): Boolean {
val root = service.rootInActiveWindow ?: return false
val commentNodes = mutableListOf<AccessibilityNodeInfo>()
findNodesByPartialText(root, "تعليق", commentNodes)
findNodesByPartialText(root, "Comment", commentNodes)
for (node in commentNodes) {
if (node.isClickable && node.performAction(AccessibilityNodeInfo.ACTION_CLICK)) return true
var parent = node.parent
var depth = 0
while (parent != null && depth < 2) {
if (parent.isClickable && parent.performAction(AccessibilityNodeInfo.ACTION_CLICK)) {
return true
}
parent = parent.parent
depth++
}
}
return false
}
private fun findNodesByPartialText(node: AccessibilityNodeInfo?, target: String, list: MutableList<AccessibilityNodeInfo>) {
if (node == null) return
if (!node.isVisibleToUser) return
val textStr = node.text?.toString() ?: ""
val descStr = node.contentDescription?.toString() ?: ""
if (textStr.contains(target, ignoreCase = true) || descStr.contains(target, ignoreCase = true)) {
list.add(node)
}
for (i in 0 until node.childCount) {
findNodesByPartialText(node.getChild(i), target, list)
}
}
private fun findNodeByHint(node: AccessibilityNodeInfo?, hint: String): AccessibilityNodeInfo? {
if (node == null) return null
if (node.hintText?.toString()?.contains(hint, ignoreCase = true) == true) return node
@@ -7,12 +7,16 @@ import android.os.Bundle
import android.view.accessibility.AccessibilityNodeInfo
import kotlinx.coroutines.delay
import com.siro.socialmedia_bot.data.SeenPostDatabase
/**
* Handles navigation and interaction within the Telegram app.
* Telegram's package name: org.telegram.messenger
*/
class TelegramNavigator(private val service: AccessibilityService) {
private val seenDb = SeenPostDatabase(service)
suspend fun openApp() {
val intent = service.packageManager.getLaunchIntentForPackage("org.telegram.messenger")
if (intent != null) {
@@ -82,8 +86,11 @@ class TelegramNavigator(private val service: AccessibilityService) {
// Filter short words
if (!validText.isNullOrEmpty() && validText.length > 20) {
if (!seenDb.isPostSeen(validText)) {
android.util.Log.d("ScrapedText", "Extracted: $validText")
results.add(validText)
seenDb.markPostAsSeen(validText)
}
}
}