diff --git a/android/app/src/main/kotlin/com/example/sham_robot/ShamAccessibilityService.kt b/android/app/src/main/kotlin/com/example/sham_robot/ShamAccessibilityService.kt index fcf9338..9145f05 100644 --- a/android/app/src/main/kotlin/com/example/sham_robot/ShamAccessibilityService.kt +++ b/android/app/src/main/kotlin/com/example/sham_robot/ShamAccessibilityService.kt @@ -14,6 +14,8 @@ import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityNodeInfo import androidx.core.app.NotificationCompat import java.io.IOException +import java.util.ArrayList +import java.util.HashSet import java.util.Random import okhttp3.* import okhttp3.MediaType.Companion.toMediaTypeOrNull @@ -27,116 +29,156 @@ class ShamAccessibilityService : AccessibilityService() { private val client = OkHttpClient() private val processedIds = HashSet() - // إعدادات التحديث التلقائي private val handler = Handler(Looper.getMainLooper()) private val random = Random() - private val minRefreshTime = 70000L // 100 ثانية - private val maxRefreshTime = 140000L // 3 دقائق - // عدل هذين السطرين مؤقتاً للتجربة - // private val minRefreshTime = 10000L // 10 ثواني - // private val maxRefreshTime = 15000L // 15 ثانية + private val minRefreshTime = 70000L + private val maxRefreshTime = 140000L - // إعدادات الإشعارات private val CHANNEL_ID = "ShamBotChannel" private val NOTIFICATION_ID_REFRESH = 1 private var NOTIFICATION_ID_TRANSACTION = 2 override fun onServiceConnected() { super.onServiceConnected() - Log.d("ShamBot", "✅ Service Connected.") + Log.d("ShamBot", "✅ Service Connected. V15 Direct Line Active!") createNotificationChannel() - - // محاولة إظهار إشعار ترحيبي للتأكد من القناة - showNotification("تم تفعيل البوت", "البوت جاهز للعمل 🤖", NOTIFICATION_ID_REFRESH, false) - + showNotification( + "تم تفعيل البوت", + "V15: القراءة المباشرة للأسطر 📜", + NOTIFICATION_ID_REFRESH, + false + ) scheduleNextRefresh() } override fun onAccessibilityEvent(event: AccessibilityEvent?) { val rootNode = rootInActiveWindow ?: return - findTransactions(rootNode) + + // تجميع وتفتيت النصوص (Splitter) + val allScreenLines = ArrayList() + collectAndSplitScreenText(rootNode, allScreenLines) + + // التحليل + processScreenContent(allScreenLines) } - private fun findTransactions(node: AccessibilityNodeInfo?) { + // 🛠️ دالة التفتيت (Splitter) + private fun collectAndSplitScreenText(node: AccessibilityNodeInfo?, list: MutableList) { node ?: return val text = node.text?.toString() val contentDesc = node.contentDescription?.toString() - val fullData = contentDesc ?: text + val rawData = contentDesc ?: text - if (fullData != null && - fullData.contains("#") && - (fullData.contains("ل.س") || fullData.contains("SYP")) - ) { - val transaction = parseData(fullData) - - if (transaction != null) { - val txId = transaction.optString("id") - val amount = transaction.optString("amount") - - if (!processedIds.contains(txId)) { - Log.d("ShamBot", "🚀 New Transaction: $txId") - processedIds.add(txId) - sendToServer(transaction) - - showNotification( - "💰 وصلتك حوالة!", - "$amount ل.س - رقم: $txId", - NOTIFICATION_ID_TRANSACTION++, - true - ) - - if (processedIds.size > 100) processedIds.clear() + if (!rawData.isNullOrEmpty()) { + // تقسيم النص إلى أسطر + val lines = rawData.split("\n") + for (line in lines) { + if (line.trim().isNotEmpty()) { + list.add(line.trim()) } } } for (i in 0 until node.childCount) { - findTransactions(node.getChild(i)) + collectAndSplitScreenText(node.getChild(i), list) } } - private fun parseData(rawData: String): JSONObject? { - try { - val lines = rawData.split("\n").map { it.trim() }.filter { it.isNotEmpty() } - var txId = "" - var amount = "" - var username = "Unknown" - var note = "" + // 🔥 دالة التحليل (V15 - Direct Line Logic) + private fun processScreenContent(lines: List) { + var i = 0 + while (i < lines.size) { + val currentLine = lines[i].trim() - if (lines.isNotEmpty() && !lines[0].contains("#")) username = lines[0] + // 1. استخراج الـ ID + val idMatch = Regex("#([0-9]+)").find(currentLine) - lines.forEachIndexed { index, line -> - if (line.contains("#")) txId = line.replace(Regex("[^0-9]"), "") - if (line.contains("ل.س") || line.contains("SYP")) { - if (index > 0) amount = lines[index - 1].replace(Regex("[^0-9.]"), "") + if (idMatch != null) { + val txId = idMatch.groupValues[1] + + // نتأكد أن الـ ID صحيح + if (txId.length in 5..12 && !processedIds.contains(txId)) { + + var amount = "" + var note = "" + + // 2. استخراج المبلغ (السطر التالي مباشرة) 💰 + // حسب الـ Dump، المبلغ يأتي دائماً في السطر التالي للـ ID + if (i + 1 < lines.size) { + val nextLine = lines[i + 1].trim() + // تنظيف السطر من أي شيء ليس رقماً + val potentialAmount = nextLine.replace(Regex("[^0-9.]"), "") + + // إذا كان السطر يحتوي على أرقام، فهو المبلغ + if (potentialAmount.isNotEmpty()) { + amount = potentialAmount + } + } + + // 3. استخراج الملاحظة (البحث عن رقم سداسي في الأسطر التالية) 🎯 + // نبحث في الـ 6 أسطر التالية + for (k in 1..6) { + if (i + k < lines.size) { + val line = lines[i + k].trim() + val cleanDigits = line.replace(Regex("[^0-9]"), "") + + // شروط الملاحظة: + // 1. رقم سداسي (6 خانات) + // 2. السطر كله رقم (أو أغلبه) + // 3. ليس هو الـ ID وليس هو المبلغ + // 4. لا يبدأ بـ 202 (ليس سنة) + + if (cleanDigits.length == 6 && + line.matches(Regex("^[0-9]+$")) && + cleanDigits != txId && + !cleanDigits.startsWith("202") + ) { + note = cleanDigits + break // وجدنا الملاحظة + } + } + } + + // 4. الإرسال + // نرسل إذا وجدنا المبلغ + if (amount.isNotEmpty()) { + val amountClean = amount.replace(".", "") + + // تجهيز الملاحظة للإرسال + val logNote = if (note.isNotEmpty()) note else "" // نرسلها فارغة لو لم توجد + + Log.d( + "ShamBot", + "🚀 CAPTURED -> ID:$txId | Amt:$amountClean | Note:$logNote" + ) + + val json = JSONObject() + json.put("id", txId) + json.put("username", "User") + json.put("amount", amountClean) + json.put("note", logNote) + json.put("source", "android_v15_direct_line") + + processedIds.add(txId) + sendToServer(json) + + val notifText = + if (logNote.isNotEmpty()) "$amountClean (#$txId) Inv:$logNote" + else "$amountClean (#$txId)" + showNotification( + "💰 عملية جديدة", + notifText, + NOTIFICATION_ID_TRANSACTION++, + true + ) + + if (processedIds.size > 200) processedIds.clear() + } } } - - if (lines.size > 2) { - val lastLine = lines.last() - val isNotDate = - !lastLine.contains("2025") && - !lastLine.contains("/") && - !lastLine.contains(":") - val isNotId = !lastLine.contains("#") - val isNotCurrency = !lastLine.contains("ل.س") - if (isNotDate && isNotId && isNotCurrency) note = lastLine - } - - if (txId.length > 4 && amount.isNotEmpty()) { - val json = JSONObject() - json.put("id", txId) - json.put("username", username) - json.put("amount", amount) - json.put("note", note) - json.put("source", "android_bot_v5_swipe_fix") - return json - } - } catch (e: Exception) { - Log.e("ShamBot", "Parsing Error", e) + i++ } - return null } private fun sendToServer(json: JSONObject) { @@ -151,8 +193,8 @@ class ShamAccessibilityService : AccessibilityService() { Log.e("ShamBot", "Network Error", e) processedIds.remove(json.optString("id")) } - override fun onResponse(call: Call, response: Response) { + // طباعة رد السيرفر للتأكد Log.d("ShamBot", "✅ Server Response: ${response.code}") response.close() } @@ -160,48 +202,34 @@ class ShamAccessibilityService : AccessibilityService() { ) } - // --- 🔔 Notifications Setup --- + // --- بقية الكود (Notifications & Refresh) --- private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val name = "Sham Bot Alerts" - val descriptionText = "Show transactions and status" - val importance = NotificationManager.IMPORTANCE_HIGH // رفعنا الأهمية لتظهر فوق - val channel = - NotificationChannel(CHANNEL_ID, name, importance).apply { - description = descriptionText - } - val notificationManager: NotificationManager = + val importance = NotificationManager.IMPORTANCE_HIGH + val channel = NotificationChannel(CHANNEL_ID, name, importance) + val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.createNotificationChannel(channel) } } private fun showNotification(title: String, content: String, id: Int, sound: Boolean) { - // التأكد من أن الإشعار سيظهر حتى لو كان صامتاً val builder = NotificationCompat.Builder(this, CHANNEL_ID) - .setSmallIcon(android.R.drawable.stat_sys_download_done) // أيقونة أوضح + .setSmallIcon(android.R.drawable.stat_sys_download_done) .setContentTitle(title) .setContentText(content) - .setPriority(NotificationCompat.PRIORITY_HIGH) // أولوية قصوى + .setPriority(NotificationCompat.PRIORITY_HIGH) .setAutoCancel(true) - - if (!sound) { - builder.setSound(null) - builder.setVibrate(longArrayOf(0L)) - } else { - builder.setDefaults(NotificationCompat.DEFAULT_ALL) - } - + if (!sound) builder.setSound(null) val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.notify(id, builder.build()) } - // --- 🤖 AUTO REFRESH LOGIC (V6 - The Heavy Pull Fix) --- private fun scheduleNextRefresh() { val delay = minRefreshTime + random.nextInt((maxRefreshTime - minRefreshTime).toInt()) - Log.d("ShamBot", "⏳ Next refresh in ${delay / 1000} seconds") handler.postDelayed( { performSwipeRefresh() @@ -212,53 +240,22 @@ class ShamAccessibilityService : AccessibilityService() { } private fun performSwipeRefresh() { - Log.d("ShamBot", "🔄 Refreshing (ULTRA SLOW & LONG Swipe)...") - // لا داعي للإشعار الصامت هنا لأنه يظهر ويختفي بسرعة وقد يربك المستخدم، نكتفي باللوج والسحب - // الفعلي - // showNotification("🔄 جاري التحديث", "سحب قوي...", NOTIFICATION_ID_REFRESH, false) - + Log.d("ShamBot", "🔄 Refreshing...") val displayMetrics = resources.displayMetrics - // استخدام toFloat() لضمان دقة الحسابات val screenHeight = displayMetrics.heightPixels.toFloat() - val screenWidth = displayMetrics.widthPixels.toFloat() - - val centerX = screenWidth / 2 - - // 🛠️ التعديل الجذري V6: سحب أبطأ وأعمق - // نبدأ من ربع الشاشة (لضمان أننا تحت الـ App Bar تماماً) - val startY = screenHeight * 0.25f - // ننزل إلى ما قبل القاع بقليل (سحبة طويلة جداً) - val endY = screenHeight * 0.90f - - val path = Path() - path.moveTo(centerX, startY) - path.lineTo(centerX, endY) - - // 💡 السر هنا: زيادة المدة الزمنية بشكل كبير. - // 500ms سريعة جداً وتعتبر "نقر" أو "Fling". - // 1500ms (ثانية ونصف) تعتبر سحباً "ثقيلاً" ومتعمداً يفهمه التطبيق كتحديث. - val duration = 1500L // ثانية ونصف - + val centerX = displayMetrics.widthPixels.toFloat() / 2 + val startY = screenHeight * 0.20f + val endY = screenHeight * 0.85f + val path = + Path().apply { + moveTo(centerX, startY) + lineTo(centerX, endY) + } val gesture = GestureDescription.Builder() - .addStroke(GestureDescription.StrokeDescription(path, 0, duration)) + .addStroke(GestureDescription.StrokeDescription(path, 0, 1500L)) .build() - - dispatchGesture( - gesture, - object : GestureResultCallback() { - override fun onCompleted(gestureDescription: GestureDescription?) { - Log.d("ShamBot", "✅ Ultra Swipe Sent Successfully") - } - override fun onCancelled(gestureDescription: GestureDescription?) { - Log.d( - "ShamBot", - "⚠️ Ultra Swipe Cancelled (App might be obscuring the view)" - ) - } - }, - null - ) + dispatchGesture(gesture, null, null) } override fun onInterrupt() {} diff --git a/android/app/src/main/res/xml/accessibility_service_config.xml b/android/app/src/main/res/xml/accessibility_service_config.xml index 14e6fd8..67740b4 100644 --- a/android/app/src/main/res/xml/accessibility_service_config.xml +++ b/android/app/src/main/res/xml/accessibility_service_config.xml @@ -1,9 +1,9 @@ \ No newline at end of file + android:canPerformGestures="true" /> \ No newline at end of file