#include "GetCardInfoScript.h" #include #include #include #include "adb/AdbUtils.h" #include "dao/MaterialDAO.h" #include "dao/BankProfileDAO.h" #include "dao/DeviceDAO.h" #include "dump/TaskDumpRecorder.h" #include "net/NetworkService.h" #include "net/MaterialPayload.h" namespace Black { GetCardInfoScript::GetCardInfoScript( QString deviceId, QString appCode, QObject *parent ) : CommonScript(parent), m_deviceId(std::move(deviceId)), m_appCode(std::move(appCode)) { } GetCardInfoScript::~GetCardInfoScript() = default; void GetCardInfoScript::doStart() { DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId); if (device.id.isEmpty()) device = DeviceDAO::findByAdbSerial(m_deviceId); if (!device.adbSerial.isEmpty()) m_deviceId = device.adbSerial; const BankProfileInfo app = BankProfileDAO::getApplication(device.id, m_appCode); if (device.id.isEmpty() || app.id == -1) { m_error = "Device or app not found: " + m_deviceId + " " + m_appCode; qCritical() << m_error; emit finishedWithResult(m_error); return; } const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone, device.id}; TaskDumpRecorder::Scope dumpScope("black", "FETCH_CARDS", m_deviceId, dumpMeta, &m_error); // Читаем текущий экран xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); closeGooglePlayBannerIfVisible(m_deviceId, device.screenWidth, device.screenHeight); // Если не на домашнем экране — перезапускаем if (!xmlScreenParser.isHomeScreen()) { qDebug() << "[Black::GetCardInfo] Not on home screen, restarting app..."; if (!runAppAndGoToHomeScreen(m_deviceId, app.package, app.pinCode, device.screenWidth, device.screenHeight)) { m_error = "Cannot reach home screen: " + device.name + " (" + m_deviceId + ") " + m_appCode; qWarning() << m_error; AdbUtils::tryToKillApplication(m_deviceId, app.package); emit finishedWithResult(m_error); return; } xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); closeGooglePlayBannerIfVisible(m_deviceId, device.screenWidth, device.screenHeight); } // 1. Парсим баланс с домашнего экрана const double balance = xmlScreenParser.parseBalanceFromHomeScreen(); qDebug() << "[Black::GetCardInfo] balance from home:" << balance; BankProfileDAO::updateAmount(app.id, balance); // 2. Нажимаем "Tu CVU" на домашнем экране Node cvuButton = xmlScreenParser.findButtonNode("Tu CVU", false); if (cvuButton.isEmpty()) { m_error = "Cannot find Tu CVU button: " + m_deviceId; qWarning() << m_error; AdbUtils::tryToKillApplication(m_deviceId, app.package); emit finishedWithResult(m_error); return; } AdbUtils::makeTap(m_deviceId, cvuButton.x(), cvuButton.y()); QThread::msleep(2000); // 3. Ждём экран CVU bool cvuScreenFound = false; for (int attempt = 0; attempt < 5; ++attempt) { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); closeGooglePlayBannerIfVisible(m_deviceId, device.screenWidth, device.screenHeight); if (xmlScreenParser.isCVUScreen()) { cvuScreenFound = true; break; } QThread::msleep(1000); } if (!cvuScreenFound) { m_error = "CVU screen not detected: " + m_deviceId; qWarning() << m_error; AdbUtils::tryToKillApplication(m_deviceId, app.package); emit finishedWithResult(m_error); return; } // 4. Парсим номер CVU const QString cvuNumber = xmlScreenParser.parseCVUNumber(); qDebug() << "[Black::GetCardInfo] CVU number:" << cvuNumber; if (cvuNumber.isEmpty()) { m_error = "Cannot parse CVU number: " + m_deviceId; qWarning() << m_error; AdbUtils::tryToKillApplication(m_deviceId, app.package); emit finishedWithResult(m_error); return; } // 5. Сохраняем аккаунт в БД (lastNumbers = последние 4 цифры CVU) const QString lastNumbers = cvuNumber.right(4); MaterialInfo account; account.deviceId = device.id; account.appCode = m_appCode; account.lastNumbers = lastNumbers; account.cardNumber = cvuNumber; account.amount = balance; account.currency = "ARS"; account.description = "CVU"; account.status = MaterialStatus::Off; account.updateTime = QDateTime::currentDateTimeUtc(); if (!MaterialDAO::upsertAccount(account)) { qWarning() << "[Black::GetCardInfo] Failed to upsert account"; } // 6. Отправляем материал на сервер. // Инвариант: на сервере одновременно живёт максимум один материал на (deviceId, appCode) — // тот, что локально active. Пушим только active-карту и только если другой // материал ещё не синхронизирован. const MaterialInfo savedAccount = MaterialDAO::findAppAccount(device.id, m_appCode, lastNumbers); const BankProfileInfo updatedApp = BankProfileDAO::getApplication(device.id, m_appCode); if (savedAccount.id != -1 && !updatedApp.bankProfileId.isEmpty()) { if (savedAccount.status != MaterialStatus::Active) { qDebug() << "[Black::GetCardInfo] CVU" << lastNumbers << "is off, not pushing to server"; } else if (!savedAccount.materialId.isEmpty()) { qDebug() << "[Black::GetCardInfo] material already synced for CVU" << lastNumbers << ", skipping"; } else { bool otherSynced = false; for (const MaterialInfo &m : MaterialDAO::getAccounts(device.id, m_appCode)) { if (m.id != savedAccount.id && !m.materialId.isEmpty()) { otherSynced = true; break; } } if (otherSynced) { qDebug() << "[Black::GetCardInfo] another material already synced in app, skipping POST for CVU" << lastNumbers; } else { NetworkService ns; ns.setDeviceId(m_deviceId); ns.setPayload(buildMaterialPayload(savedAccount, updatedApp)); ns.addExtra("account_id", savedAccount.id); ns.postMaterial(); qDebug() << "[Black::GetCardInfo] postMaterial done for CVU" << lastNumbers; } } } else { qWarning() << "[Black::GetCardInfo] bankProfileId empty or account not found, skipping material"; } qDebug() << "[Black::GetCardInfo] Done. CVU:" << cvuNumber << "balance:" << balance; AdbUtils::tryToKillApplication(m_deviceId, app.package); emit finishedWithResult(m_error); } } // namespace Black