2026-02-16

This commit is contained in:
Hamza-Ayed
2026-02-16 21:58:50 +03:00
parent 3037298dac
commit b955da760b
2 changed files with 141 additions and 144 deletions

View File

@@ -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<String>()
// إعدادات التحديث التلقائي
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<String>()
collectAndSplitScreenText(rootNode, allScreenLines)
// التحليل
processScreenContent(allScreenLines)
}
private fun findTransactions(node: AccessibilityNodeInfo?) {
// 🛠️ دالة التفتيت (Splitter)
private fun collectAndSplitScreenText(node: AccessibilityNodeInfo?, list: MutableList<String>) {
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 = ""
// 🔥 دالة التحليل (V15 - Direct Line Logic)
private fun processScreenContent(lines: List<String>) {
var i = 0
while (i < lines.size) {
val currentLine = lines[i].trim()
// 1. استخراج الـ ID
val idMatch = Regex("#([0-9]+)").find(currentLine)
if (idMatch != null) {
val txId = idMatch.groupValues[1]
// نتأكد أن الـ ID صحيح
if (txId.length in 5..12 && !processedIds.contains(txId)) {
var amount = ""
var username = "Unknown"
var note = ""
if (lines.isNotEmpty() && !lines[0].contains("#")) username = lines[0]
// 2. استخراج المبلغ (السطر التالي مباشرة) 💰
// حسب الـ Dump، المبلغ يأتي دائماً في السطر التالي للـ ID
if (i + 1 < lines.size) {
val nextLine = lines[i + 1].trim()
// تنظيف السطر من أي شيء ليس رقماً
val potentialAmount = nextLine.replace(Regex("[^0-9.]"), "")
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 (potentialAmount.isNotEmpty()) {
amount = potentialAmount
}
}
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
// 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 // وجدنا الملاحظة
}
}
}
if (txId.length > 4 && amount.isNotEmpty()) {
// 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", username)
json.put("amount", amount)
json.put("note", note)
json.put("source", "android_bot_v5_swipe_fix")
return json
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()
}
} catch (e: Exception) {
Log.e("ShamBot", "Parsing Error", e)
}
return null
}
i++
}
}
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() {}

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/accessibility_service_description"
android:packageNames="com.shmacash.shamcash"
android:accessibilityEventTypes="typeWindowContentChanged|typeWindowStateChanged"
android:accessibilityFlags="flagDefault|flagIncludeNotImportantViews|flagReportViewIds"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFlags="flagDefault|flagIncludeNotImportantViews|flagReportViewIds|flagRetrieveInteractiveWindows"
android:accessibilityFeedbackType="feedbackGeneric"
android:notificationTimeout="100"
android:canRetrieveWindowContent="true"
android:canPerformGestures="true"
android:notificationTimeout="100" />
android:canPerformGestures="true" />