Initial commit for service

This commit is contained in:
Hamza-Ayed
2026-01-20 23:38:50 +03:00
commit 2780f86ca0
136 changed files with 5396 additions and 0 deletions

14
android/.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

View File

@@ -0,0 +1,48 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.example.sham_robot"
compileSdk = flutter.compileSdkVersion
ndkVersion = "27.0.12077973"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.sham_robot"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
dependencies {
implementation("androidx.core:core-ktx:1.10.1")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0") // أو النسخة المتوافقة معك
implementation("com.squareup.okhttp3:okhttp:4.11.0") // صيغة كوتلن الصحيحة
}
flutter {
source = "../.."
}

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@@ -0,0 +1,54 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.sham_robot">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:label="sham_robot"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<meta-data
android:name="io.flutter.embedding.android.EnableImpeller"
android:value="false" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".ShamAccessibilityService"
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/accessibility_service_config" />
</service>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain" />
</intent>
</queries>
</manifest>

View File

@@ -0,0 +1,5 @@
package com.example.sham_robot
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()

View File

@@ -0,0 +1,265 @@
package com.example.sham_robot
import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.GestureDescription
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.graphics.Path
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import androidx.core.app.NotificationCompat
import java.io.IOException
import java.util.Random
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
class ShamAccessibilityService : AccessibilityService() {
private val SERVER_URL =
"https://walletintaleq.intaleq.xyz/v1/main/ride/shamcash/save_transactions.php"
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 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.")
createNotificationChannel()
// محاولة إظهار إشعار ترحيبي للتأكد من القناة
showNotification("تم تفعيل البوت", "البوت جاهز للعمل 🤖", NOTIFICATION_ID_REFRESH, false)
scheduleNextRefresh()
}
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
val rootNode = rootInActiveWindow ?: return
findTransactions(rootNode)
}
private fun findTransactions(node: AccessibilityNodeInfo?) {
node ?: return
val text = node.text?.toString()
val contentDesc = node.contentDescription?.toString()
val fullData = 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()
}
}
}
for (i in 0 until node.childCount) {
findTransactions(node.getChild(i))
}
}
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 = ""
if (lines.isNotEmpty() && !lines[0].contains("#")) username = lines[0]
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 (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) {
val mediaType = "application/json; charset=utf-8".toMediaTypeOrNull()
val body = json.toString().toRequestBody(mediaType)
val request = Request.Builder().url(SERVER_URL).post(body).build()
client.newCall(request)
.enqueue(
object : Callback {
override fun onFailure(call: Call, e: IOException) {
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()
}
}
)
}
// --- 🔔 Notifications Setup ---
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 =
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) // أيقونة أوضح
.setContentTitle(title)
.setContentText(content)
.setPriority(NotificationCompat.PRIORITY_HIGH) // أولوية قصوى
.setAutoCancel(true)
if (!sound) {
builder.setSound(null)
builder.setVibrate(longArrayOf(0L))
} else {
builder.setDefaults(NotificationCompat.DEFAULT_ALL)
}
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()
scheduleNextRefresh()
},
delay
)
}
private fun performSwipeRefresh() {
Log.d("ShamBot", "🔄 Refreshing (ULTRA SLOW & LONG Swipe)...")
// لا داعي للإشعار الصامت هنا لأنه يظهر ويختفي بسرعة وقد يربك المستخدم، نكتفي باللوج والسحب
// الفعلي
// showNotification("🔄 جاري التحديث", "سحب قوي...", NOTIFICATION_ID_REFRESH, false)
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 gesture =
GestureDescription.Builder()
.addStroke(GestureDescription.StrokeDescription(path, 0, duration))
.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
)
}
override fun onInterrupt() {}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="accessibility_service_description">Sham Robot for Transactions Automation</string>
</resources>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +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:canRetrieveWindowContent="true"
android:canPerformGestures="true"
android:notificationTimeout="100" />

View File

@@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

21
android/build.gradle.kts Normal file
View File

@@ -0,0 +1,21 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

View File

@@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

View File

@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip

View File

@@ -0,0 +1,25 @@
pluginManagement {
val flutterSdkPath = run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.7.3" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
}
include(":app")