2026-02-16
This commit is contained in:
@@ -14,6 +14,8 @@ import android.view.accessibility.AccessibilityEvent
|
|||||||
import android.view.accessibility.AccessibilityNodeInfo
|
import android.view.accessibility.AccessibilityNodeInfo
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.util.ArrayList
|
||||||
|
import java.util.HashSet
|
||||||
import java.util.Random
|
import java.util.Random
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
@@ -27,116 +29,156 @@ class ShamAccessibilityService : AccessibilityService() {
|
|||||||
private val client = OkHttpClient()
|
private val client = OkHttpClient()
|
||||||
private val processedIds = HashSet<String>()
|
private val processedIds = HashSet<String>()
|
||||||
|
|
||||||
// إعدادات التحديث التلقائي
|
|
||||||
private val handler = Handler(Looper.getMainLooper())
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
private val random = Random()
|
private val random = Random()
|
||||||
private val minRefreshTime = 70000L // 100 ثانية
|
private val minRefreshTime = 70000L
|
||||||
private val maxRefreshTime = 140000L // 3 دقائق
|
private val maxRefreshTime = 140000L
|
||||||
// عدل هذين السطرين مؤقتاً للتجربة
|
|
||||||
// private val minRefreshTime = 10000L // 10 ثواني
|
|
||||||
// private val maxRefreshTime = 15000L // 15 ثانية
|
|
||||||
|
|
||||||
// إعدادات الإشعارات
|
|
||||||
private val CHANNEL_ID = "ShamBotChannel"
|
private val CHANNEL_ID = "ShamBotChannel"
|
||||||
private val NOTIFICATION_ID_REFRESH = 1
|
private val NOTIFICATION_ID_REFRESH = 1
|
||||||
private var NOTIFICATION_ID_TRANSACTION = 2
|
private var NOTIFICATION_ID_TRANSACTION = 2
|
||||||
|
|
||||||
override fun onServiceConnected() {
|
override fun onServiceConnected() {
|
||||||
super.onServiceConnected()
|
super.onServiceConnected()
|
||||||
Log.d("ShamBot", "✅ Service Connected.")
|
Log.d("ShamBot", "✅ Service Connected. V15 Direct Line Active!")
|
||||||
createNotificationChannel()
|
createNotificationChannel()
|
||||||
|
showNotification(
|
||||||
// محاولة إظهار إشعار ترحيبي للتأكد من القناة
|
"تم تفعيل البوت",
|
||||||
showNotification("تم تفعيل البوت", "البوت جاهز للعمل 🤖", NOTIFICATION_ID_REFRESH, false)
|
"V15: القراءة المباشرة للأسطر 📜",
|
||||||
|
NOTIFICATION_ID_REFRESH,
|
||||||
|
false
|
||||||
|
)
|
||||||
scheduleNextRefresh()
|
scheduleNextRefresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
|
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
|
||||||
val rootNode = rootInActiveWindow ?: return
|
val rootNode = rootInActiveWindow ?: return
|
||||||
findTransactions(rootNode)
|
|
||||||
|
// تجميع وتفتيت النصوص (Splitter)
|
||||||
|
val allScreenLines = ArrayList<String>()
|
||||||
|
collectAndSplitScreenText(rootNode, allScreenLines)
|
||||||
|
|
||||||
|
// التحليل
|
||||||
|
processScreenContent(allScreenLines)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findTransactions(node: AccessibilityNodeInfo?) {
|
// 🛠️ دالة التفتيت (Splitter)
|
||||||
|
private fun collectAndSplitScreenText(node: AccessibilityNodeInfo?, list: MutableList<String>) {
|
||||||
node ?: return
|
node ?: return
|
||||||
|
|
||||||
val text = node.text?.toString()
|
val text = node.text?.toString()
|
||||||
val contentDesc = node.contentDescription?.toString()
|
val contentDesc = node.contentDescription?.toString()
|
||||||
val fullData = contentDesc ?: text
|
val rawData = contentDesc ?: text
|
||||||
|
|
||||||
if (fullData != null &&
|
if (!rawData.isNullOrEmpty()) {
|
||||||
fullData.contains("#") &&
|
// تقسيم النص إلى أسطر
|
||||||
(fullData.contains("ل.س") || fullData.contains("SYP"))
|
val lines = rawData.split("\n")
|
||||||
) {
|
for (line in lines) {
|
||||||
val transaction = parseData(fullData)
|
if (line.trim().isNotEmpty()) {
|
||||||
|
list.add(line.trim())
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i in 0 until node.childCount) {
|
for (i in 0 until node.childCount) {
|
||||||
findTransactions(node.getChild(i))
|
collectAndSplitScreenText(node.getChild(i), list)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseData(rawData: String): JSONObject? {
|
// 🔥 دالة التحليل (V15 - Direct Line Logic)
|
||||||
try {
|
private fun processScreenContent(lines: List<String>) {
|
||||||
val lines = rawData.split("\n").map { it.trim() }.filter { it.isNotEmpty() }
|
var i = 0
|
||||||
var txId = ""
|
while (i < lines.size) {
|
||||||
var amount = ""
|
val currentLine = lines[i].trim()
|
||||||
var username = "Unknown"
|
|
||||||
var note = ""
|
|
||||||
|
|
||||||
if (lines.isNotEmpty() && !lines[0].contains("#")) username = lines[0]
|
// 1. استخراج الـ ID
|
||||||
|
val idMatch = Regex("#([0-9]+)").find(currentLine)
|
||||||
|
|
||||||
lines.forEachIndexed { index, line ->
|
if (idMatch != null) {
|
||||||
if (line.contains("#")) txId = line.replace(Regex("[^0-9]"), "")
|
val txId = idMatch.groupValues[1]
|
||||||
if (line.contains("ل.س") || line.contains("SYP")) {
|
|
||||||
if (index > 0) amount = lines[index - 1].replace(Regex("[^0-9.]"), "")
|
// نتأكد أن الـ 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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
i++
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendToServer(json: JSONObject) {
|
private fun sendToServer(json: JSONObject) {
|
||||||
@@ -151,8 +193,8 @@ class ShamAccessibilityService : AccessibilityService() {
|
|||||||
Log.e("ShamBot", "Network Error", e)
|
Log.e("ShamBot", "Network Error", e)
|
||||||
processedIds.remove(json.optString("id"))
|
processedIds.remove(json.optString("id"))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
// طباعة رد السيرفر للتأكد
|
||||||
Log.d("ShamBot", "✅ Server Response: ${response.code}")
|
Log.d("ShamBot", "✅ Server Response: ${response.code}")
|
||||||
response.close()
|
response.close()
|
||||||
}
|
}
|
||||||
@@ -160,48 +202,34 @@ class ShamAccessibilityService : AccessibilityService() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 🔔 Notifications Setup ---
|
// --- بقية الكود (Notifications & Refresh) ---
|
||||||
private fun createNotificationChannel() {
|
private fun createNotificationChannel() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val name = "Sham Bot Alerts"
|
val name = "Sham Bot Alerts"
|
||||||
val descriptionText = "Show transactions and status"
|
val importance = NotificationManager.IMPORTANCE_HIGH
|
||||||
val importance = NotificationManager.IMPORTANCE_HIGH // رفعنا الأهمية لتظهر فوق
|
val channel = NotificationChannel(CHANNEL_ID, name, importance)
|
||||||
val channel =
|
val notificationManager =
|
||||||
NotificationChannel(CHANNEL_ID, name, importance).apply {
|
|
||||||
description = descriptionText
|
|
||||||
}
|
|
||||||
val notificationManager: NotificationManager =
|
|
||||||
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
notificationManager.createNotificationChannel(channel)
|
notificationManager.createNotificationChannel(channel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showNotification(title: String, content: String, id: Int, sound: Boolean) {
|
private fun showNotification(title: String, content: String, id: Int, sound: Boolean) {
|
||||||
// التأكد من أن الإشعار سيظهر حتى لو كان صامتاً
|
|
||||||
val builder =
|
val builder =
|
||||||
NotificationCompat.Builder(this, CHANNEL_ID)
|
NotificationCompat.Builder(this, CHANNEL_ID)
|
||||||
.setSmallIcon(android.R.drawable.stat_sys_download_done) // أيقونة أوضح
|
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
.setContentText(content)
|
.setContentText(content)
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH) // أولوية قصوى
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
|
if (!sound) builder.setSound(null)
|
||||||
if (!sound) {
|
|
||||||
builder.setSound(null)
|
|
||||||
builder.setVibrate(longArrayOf(0L))
|
|
||||||
} else {
|
|
||||||
builder.setDefaults(NotificationCompat.DEFAULT_ALL)
|
|
||||||
}
|
|
||||||
|
|
||||||
val notificationManager =
|
val notificationManager =
|
||||||
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
notificationManager.notify(id, builder.build())
|
notificationManager.notify(id, builder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 🤖 AUTO REFRESH LOGIC (V6 - The Heavy Pull Fix) ---
|
|
||||||
private fun scheduleNextRefresh() {
|
private fun scheduleNextRefresh() {
|
||||||
val delay = minRefreshTime + random.nextInt((maxRefreshTime - minRefreshTime).toInt())
|
val delay = minRefreshTime + random.nextInt((maxRefreshTime - minRefreshTime).toInt())
|
||||||
Log.d("ShamBot", "⏳ Next refresh in ${delay / 1000} seconds")
|
|
||||||
handler.postDelayed(
|
handler.postDelayed(
|
||||||
{
|
{
|
||||||
performSwipeRefresh()
|
performSwipeRefresh()
|
||||||
@@ -212,53 +240,22 @@ class ShamAccessibilityService : AccessibilityService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun performSwipeRefresh() {
|
private fun performSwipeRefresh() {
|
||||||
Log.d("ShamBot", "🔄 Refreshing (ULTRA SLOW & LONG Swipe)...")
|
Log.d("ShamBot", "🔄 Refreshing...")
|
||||||
// لا داعي للإشعار الصامت هنا لأنه يظهر ويختفي بسرعة وقد يربك المستخدم، نكتفي باللوج والسحب
|
|
||||||
// الفعلي
|
|
||||||
// showNotification("🔄 جاري التحديث", "سحب قوي...", NOTIFICATION_ID_REFRESH, false)
|
|
||||||
|
|
||||||
val displayMetrics = resources.displayMetrics
|
val displayMetrics = resources.displayMetrics
|
||||||
// استخدام toFloat() لضمان دقة الحسابات
|
|
||||||
val screenHeight = displayMetrics.heightPixels.toFloat()
|
val screenHeight = displayMetrics.heightPixels.toFloat()
|
||||||
val screenWidth = displayMetrics.widthPixels.toFloat()
|
val centerX = displayMetrics.widthPixels.toFloat() / 2
|
||||||
|
val startY = screenHeight * 0.20f
|
||||||
val centerX = screenWidth / 2
|
val endY = screenHeight * 0.85f
|
||||||
|
val path =
|
||||||
// 🛠️ التعديل الجذري V6: سحب أبطأ وأعمق
|
Path().apply {
|
||||||
// نبدأ من ربع الشاشة (لضمان أننا تحت الـ App Bar تماماً)
|
moveTo(centerX, startY)
|
||||||
val startY = screenHeight * 0.25f
|
lineTo(centerX, endY)
|
||||||
// ننزل إلى ما قبل القاع بقليل (سحبة طويلة جداً)
|
}
|
||||||
val endY = screenHeight * 0.90f
|
|
||||||
|
|
||||||
val path = Path()
|
|
||||||
path.moveTo(centerX, startY)
|
|
||||||
path.lineTo(centerX, endY)
|
|
||||||
|
|
||||||
// 💡 السر هنا: زيادة المدة الزمنية بشكل كبير.
|
|
||||||
// 500ms سريعة جداً وتعتبر "نقر" أو "Fling".
|
|
||||||
// 1500ms (ثانية ونصف) تعتبر سحباً "ثقيلاً" ومتعمداً يفهمه التطبيق كتحديث.
|
|
||||||
val duration = 1500L // ثانية ونصف
|
|
||||||
|
|
||||||
val gesture =
|
val gesture =
|
||||||
GestureDescription.Builder()
|
GestureDescription.Builder()
|
||||||
.addStroke(GestureDescription.StrokeDescription(path, 0, duration))
|
.addStroke(GestureDescription.StrokeDescription(path, 0, 1500L))
|
||||||
.build()
|
.build()
|
||||||
|
dispatchGesture(gesture, null, null)
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onInterrupt() {}
|
override fun onInterrupt() {}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
|
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:description="@string/accessibility_service_description"
|
android:description="@string/accessibility_service_description"
|
||||||
android:packageNames="com.shmacash.shamcash"
|
android:accessibilityEventTypes="typeAllMask"
|
||||||
android:accessibilityEventTypes="typeWindowContentChanged|typeWindowStateChanged"
|
android:accessibilityFlags="flagDefault|flagIncludeNotImportantViews|flagReportViewIds|flagRetrieveInteractiveWindows"
|
||||||
android:accessibilityFlags="flagDefault|flagIncludeNotImportantViews|flagReportViewIds"
|
android:accessibilityFeedbackType="feedbackGeneric"
|
||||||
|
android:notificationTimeout="100"
|
||||||
android:canRetrieveWindowContent="true"
|
android:canRetrieveWindowContent="true"
|
||||||
android:canPerformGestures="true"
|
android:canPerformGestures="true" />
|
||||||
android:notificationTimeout="100" />
|
|
||||||
Reference in New Issue
Block a user