package com.example.modifier.service import android.os.Build import android.util.Log import com.example.modifier.BuildConfig import com.example.modifier.baseTag import com.example.modifier.enums.ReqState import com.example.modifier.model.CancelStoreNumberAction import com.example.modifier.model.CheckPifAction import com.example.modifier.model.CleanBackupAction import com.example.modifier.model.InstallApkAction import com.example.modifier.model.ResumeAction import com.example.modifier.model.RunScriptAction import com.example.modifier.model.RunScriptResult import com.example.modifier.model.SocketCallback import com.example.modifier.model.StoreNumberAction import com.example.modifier.model.TaskAction import com.example.modifier.model.TaskExecutionResult import com.example.modifier.model.UpdateDeviceAction import com.example.modifier.repo.AppPrefsRepo import com.example.modifier.repo.AppStateRepo import com.example.modifier.repo.SpoofedInfoRepo import com.example.modifier.serializer.Json import com.example.modifier.utils.checkPif import com.example.modifier.utils.getIPAddress import io.socket.client.IO import io.socket.client.Socket import io.socket.emitter.Emitter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.serialization.encodeToString import org.json.JSONObject import java.util.Timer import kotlin.concurrent.schedule class SocketClient( private val taskRunner: TaskRunner ) : Emitter.Listener { companion object { private const val TAG = "$baseTag/SocketClient" } private val mSocketOpts: IO.Options = IO.Options() private val mSocket: Socket init { val appPrefs = AppPrefsRepo.instance.appPrefs.value mSocketOpts.query = "model=${Build.MODEL}&name=${appPrefs.name}&id=${appPrefs.id}&version=${BuildConfig.VERSION_CODE}&ip=${ getIPAddress().joinToString(",") }}" mSocketOpts.transports = arrayOf("websocket") mSocket = IO.socket(appPrefs.server, mSocketOpts) mSocket.on("message", this) mSocket.on(Socket.EVENT_CONNECT) { this.onConnected(it) } mSocket.on(Socket.EVENT_DISCONNECT) { this.onDisconnected(it) } mSocket.on(Socket.EVENT_CONNECT_ERROR) { it.forEach { Log.e(TAG, "Connection error", it as Throwable) } } try { mSocket.connect() } catch (e: Exception) { Log.e(TAG, "connect error", e) } val timer = Timer() timer.schedule(0, 3000) { reportDeviceStatues() } } fun disconnect() { mSocket.off() mSocket.disconnect() } private fun throwIfBusy() { if (AppStateRepo.instance.appState.value.busy) { throw Exception("Device is busy") } } override fun call(vararg args: Any?) { if (args.isEmpty()) { return } Log.i(TAG, "Received message: " + args[0]) if (args[0] !is JSONObject) return val json = args[0] as JSONObject val action = json.optString("action") val id = json.optString("id") val jsonStr = json.toString() when (action) { TaskAction.NAME -> { try { throwIfBusy() val taskAction = Json.decodeFromString(jsonStr) CoroutineScope(Dispatchers.IO).launch { taskRunner.runTask(taskAction, onSuccess = { res -> socketCallback(taskAction.id, res) }, onError = { error -> socketCallback( id = taskAction.id, status = -1, error = error.message ) }) } } catch (e: Exception) { Log.e(TAG, "taskAction error", e) socketCallback(id = id, status = -1, error = e.message) } } InstallApkAction.NAME -> { try { val installApkAction = Json.decodeFromString(jsonStr) if (true != installApkAction.data.interrupt) { throwIfBusy() } CoroutineScope(Dispatchers.IO).launch { taskRunner.installApk(installApkAction, onSuccess = { socketCallback( id = installApkAction.id, status = 0, data = "OK" ) }, onError = { error -> socketCallback( id = installApkAction.id, status = -1, error = error.message ) }) } } catch (e: Exception) { Log.e(TAG, "installApk error", e) socketCallback(id = id, status = -1, error = e.message) } } RunScriptAction.NAME -> { try { val runScriptAction = Json.decodeFromString(jsonStr) CoroutineScope(Dispatchers.IO).launch { taskRunner.runScript(runScriptAction, onSuccess = { out, err -> socketCallback(runScriptAction.id, RunScriptResult(out, err)) }, onError = { error -> socketCallback( id = runScriptAction.id, status = -1, error = error.message ) }) } } catch (e: Exception) { Log.e(TAG, "runScript error", e) socketCallback(id = id, status = -1, error = e.message) } } UpdateDeviceAction.NAME -> { try { val updateDeviceAction = Json.decodeFromString(jsonStr) CoroutineScope(Dispatchers.IO).launch { taskRunner.updateDevice(updateDeviceAction, onSuccess = { socketCallback( id = updateDeviceAction.id, status = 0, data = "OK" ) }, onError = { error -> socketCallback( id = updateDeviceAction.id, status = -1, error = error.message ) }) } } catch (e: Exception) { Log.e(TAG, "updateDevice error", e) socketCallback(id = id, status = -1, error = e.message) } } StoreNumberAction.NAME -> { try { throwIfBusy() val storeNumberAction = Json.decodeFromString(jsonStr) CoroutineScope(Dispatchers.IO).launch { taskRunner.storeNumbers(storeNumberAction.data?.num) socketCallback(id = storeNumberAction.id, status = 0, data = "OK") } } catch (e: Exception) { Log.e(TAG, "storeNumber error", e) socketCallback(id = id, status = -1, error = e.message) } } CancelStoreNumberAction.NAME -> { try { CoroutineScope(Dispatchers.IO).launch { taskRunner.cancelStoreNumber() socketCallback(id = id, status = 0, data = "OK") } } catch (e: Exception) { Log.e(TAG, "cancelStoreNumber error", e) socketCallback(id = id, status = -1, error = e.message) } } ResumeAction.NAME -> { try { throwIfBusy() val resumeAction = Json.decodeFromString(jsonStr) CoroutineScope(Dispatchers.IO).launch { AppStateRepo.instance.updateRuntimeFlags(suspended = false) AppStateRepo.instance.updateSend(true) if (resumeAction.data?.request == true) { taskRunner.requestNumberOnTask( reset = resumeAction.data.reset ?: false ) } } socketCallback(id = id, status = 0, data = "OK") } catch (e: Exception) { Log.e(TAG, "resume error", e) socketCallback(id = id, status = -1, error = e.message) } } CheckPifAction.NAME -> { try { val checkPifAction = Json.decodeFromString(jsonStr) CoroutineScope(Dispatchers.IO).launch { checkPif() socketCallback( id = checkPifAction.id, status = 0, data = "OK" ) } } catch (e: Exception) { Log.e(TAG, "checkPif error", e) socketCallback(id = id, status = -1, error = e.message) } } CleanBackupAction.NAME -> { try { val cleanBackupAction = Json.decodeFromString(jsonStr) CoroutineScope(Dispatchers.IO).launch { taskRunner.cleanBackup( cleanBackupAction.data.start, cleanBackupAction.data.end ) } socketCallback(id = id, status = 0, data = "OK") } catch (e: Exception) { Log.e(TAG, "checkPif error", e) socketCallback(id = id, status = -1, error = e.message) } } } } private fun onConnected(vararg args: Any) { Log.i(TAG, "Connected to server") CoroutineScope(Dispatchers.IO).launch { delay(500) reportDeviceStatues() } } private fun onDisconnected(vararg args: Any) {} fun reportDeviceStatues() { if (!mSocket.connected()) { return } val data = JSONObject() runCatching { data.put("action", "updateDevice") val dataObj = JSONObject() dataObj.put("canSend", AppStateRepo.instance.appState.value.send) dataObj.put("busy", AppStateRepo.instance.appState.value.busy) dataObj.put("currentCountry", SpoofedInfoRepo.instance.spoofedInfo.value.country) dataObj.put("ip", getIPAddress().joinToString(",")) dataObj.put( "requesting", AppStateRepo.instance.appState.value.reqState == ReqState.REQUEST ) dataObj.put("suspended", AppStateRepo.instance.appState.value.suspended) dataObj.put("storing", AppStateRepo.instance.appState.value.storing) data.put("data", dataObj) mSocket.emit("message", data) }.onFailure { e -> Log.e(TAG, "reportDeviceStatues error", e) } } private fun socketCallback(id: String, data: List) { Log.i(TAG, "socketCallback: ${Json.encodeToString( SocketCallback( id = id, status = 0, data = data ) )}") runCatching { mSocket.emit( "callback", JSONObject( Json.encodeToString( SocketCallback( id = id, status = 0, data = data ) ) ) ) }.onFailure { e -> Log.e(TAG, "emitEvent error", e) } } private fun socketCallback(id: String, data: RunScriptResult) { runCatching { mSocket.emit( "callback", JSONObject( Json.encodeToString( SocketCallback( id = id, status = 0, data = data ) ) ) ) }.onFailure { e -> Log.e(TAG, "emitEvent error", e) } } private fun socketCallback( id: String, status: Int, data: String? = null, error: String? = null ) { runCatching { mSocket.emit( "callback", JSONObject( Json.encodeToString( SocketCallback( id = id, status = status, data = data, error = error ) ) ) ) }.onFailure { e -> Log.e(TAG, "emitEvent error", e) } } }