398 lines
19 KiB
C++
398 lines
19 KiB
C++
#include "GetCardInfoScript.h"
|
||
|
||
#include <QDateTime>
|
||
#include <QJsonDocument>
|
||
#include <QJsonObject>
|
||
#include <QSettings>
|
||
#include <QThread>
|
||
|
||
#include "adb/AdbUtils.h"
|
||
#include "dao/MaterialDAO.h"
|
||
#include "dao/BankProfileDAO.h"
|
||
#include "dao/DeviceDAO.h"
|
||
#include "dao/SettingsDAO.h"
|
||
#include "net/NetworkService.h"
|
||
#include "AppLogger.h"
|
||
|
||
namespace Ozon {
|
||
|
||
GetCardInfoScript::GetCardInfoScript(
|
||
QString deviceId,
|
||
QString appCode,
|
||
QString lastNumbers,
|
||
QObject *parent
|
||
) : CommonScript(parent),
|
||
m_deviceId(std::move(deviceId)),
|
||
m_appCode(std::move(appCode)),
|
||
m_lastNumbers(std::move(lastNumbers)) {
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
// Читаем текущий экран
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
|
||
// Закрываем рекламный bottom sheet, если открыт
|
||
if (Node adNode = xmlScreenParser.tryToFindAdBanner(); !adNode.isEmpty()) {
|
||
qDebug() << "[GetCardInfo] Ad banner detected, closing...";
|
||
AdbUtils::makeTap(m_deviceId, adNode.x(), adNode.y());
|
||
QThread::msleep(1000);
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
}
|
||
|
||
// Свежий дамп перед проверкой домашнего экрана
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
|
||
// Если не на домашнем экране — перезапускаем приложение
|
||
if (!xmlScreenParser.isHomeScreen()) {
|
||
qDebug() << "[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: " + m_deviceId;
|
||
qWarning() << m_error;
|
||
AppLogger::log("ozon/GetCardInfo", m_deviceId, m_error,
|
||
AdbUtils::captureScreenshotBytes(m_deviceId),
|
||
"FETCH_PROFILE");
|
||
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
||
emit finishedWithResult(m_error);
|
||
return;
|
||
}
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
}
|
||
|
||
// Закрываем баннеры на главной
|
||
tryToCloseAllBannersOnHomePage(m_deviceId, device.screenWidth, device.screenHeight);
|
||
QThread::msleep(500);
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
|
||
// Переходим в профиль и парсим имя и телефон (только если ещё нет в БД)
|
||
const bool hasProfileInfo = !app.fullName.isEmpty() && !app.phone.isEmpty();
|
||
Node userNameNode;
|
||
if (!hasProfileInfo) {
|
||
userNameNode = xmlScreenParser.findUserNameOnHomeScreen(device.screenHeight);
|
||
}
|
||
if (!hasProfileInfo && !userNameNode.isEmpty()) {
|
||
qDebug() << "[GetCardInfo] Navigating to profile to parse name/phone...";
|
||
AdbUtils::makeTap(m_deviceId, userNameNode.x(), userNameNode.y());
|
||
QThread::msleep(2500);
|
||
|
||
for (int attempt = 0; attempt < 5; ++attempt) {
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
if (xmlScreenParser.isProfileScreen()) break;
|
||
QThread::msleep(1000);
|
||
}
|
||
|
||
if (xmlScreenParser.isProfileScreen()) {
|
||
const QString fullName = xmlScreenParser.parseProfileFullName();
|
||
const QString phone = xmlScreenParser.parseProfilePhone();
|
||
qDebug() << "[GetCardInfo] Profile parsed - fullName:" << fullName << "phone:" << phone;
|
||
BankProfileDAO::updateProfileInfo(app.id, fullName, phone);
|
||
} else {
|
||
qWarning() << "[GetCardInfo] Profile screen not reached, skipping profile parse";
|
||
}
|
||
|
||
AdbUtils::goBack(m_deviceId);
|
||
QThread::msleep(1500);
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
} else {
|
||
qDebug() << "[GetCardInfo] Profile info already in DB or username not found, skipping profile parse";
|
||
}
|
||
|
||
// Проверяем что на домашнем экране перед поиском карт
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
if (!xmlScreenParser.isHomeScreen()) {
|
||
qDebug() << "[GetCardInfo] Not on home screen before card search, restarting...";
|
||
if (!runAppAndGoToHomeScreen(m_deviceId, app.package, app.pinCode,
|
||
device.screenWidth, device.screenHeight)) {
|
||
m_error = "Cannot reach home screen before card search: " + m_deviceId;
|
||
qWarning() << m_error;
|
||
AppLogger::log("ozon/GetCardInfo", m_deviceId, m_error,
|
||
AdbUtils::captureScreenshotBytes(m_deviceId),
|
||
"FETCH_PROFILE");
|
||
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
||
emit finishedWithResult(m_error);
|
||
return;
|
||
}
|
||
tryToCloseAllBannersOnHomePage(m_deviceId, device.screenWidth, device.screenHeight);
|
||
QThread::msleep(500);
|
||
}
|
||
|
||
// Ждём пока все карты подгрузятся — до 5 попыток
|
||
QList<MaterialInfo> cards;
|
||
for (int attempt = 0; attempt < 5; ++attempt) {
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
cards = xmlScreenParser.parseCardsOnHomeScreen();
|
||
if (!cards.isEmpty()) break;
|
||
qDebug() << "[GetCardInfo] No cards found on home screen, attempt" << attempt + 1 << "/ 5";
|
||
QThread::msleep(1500);
|
||
}
|
||
|
||
if (cards.isEmpty()) {
|
||
m_error = "No cards found on home screen: " + m_deviceId;
|
||
qWarning() << m_error;
|
||
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
||
emit finishedWithResult(m_error);
|
||
return;
|
||
}
|
||
|
||
qDebug() << "[GetCardInfo] Found" << cards.size() << "card(s) on home screen";
|
||
|
||
// Апсёртим аккаунты для всех найденных карт
|
||
for (MaterialInfo &card : cards) {
|
||
card.deviceId = device.id;
|
||
card.appCode = m_appCode;
|
||
card.updateTime = QDateTime::currentDateTimeUtc();
|
||
card.status = MaterialStatus::Active;
|
||
if (!MaterialDAO::upsertAccount(card)) {
|
||
qWarning() << "[GetCardInfo] Failed to upsert account for card" << card.lastNumbers;
|
||
} else {
|
||
qDebug() << "[GetCardInfo] Upserted account:" << card.lastNumbers
|
||
<< card.description << card.amount;
|
||
}
|
||
}
|
||
|
||
// Сохраняем баланс домашнего экрана в application
|
||
const double totalAmount = cards.isEmpty() ? 0.0 : cards[0].amount;
|
||
if (totalAmount > 0.0) {
|
||
BankProfileDAO::updateAmount(app.id, totalAmount);
|
||
}
|
||
|
||
// Синхронная отправка на сервер (NetworkService блокируется через QEventLoop внутри)
|
||
NetworkService ns;
|
||
ns.setDeviceId(m_deviceId);
|
||
|
||
// 1. Создаём девайс если нет api_id
|
||
if (device.apiId.isEmpty()) {
|
||
QJsonObject deviceBody;
|
||
deviceBody["desktop_id"] = SettingsDAO::get("desktop_id");
|
||
deviceBody["status"] = "ONLINE";
|
||
deviceBody["name"] = device.name;
|
||
deviceBody["screen_width"] = device.screenWidth;
|
||
deviceBody["screen_height"]= device.screenHeight;
|
||
deviceBody["battery"] = device.battery;
|
||
ns.setPayload(QJsonDocument(deviceBody).toJson());
|
||
ns.createDevice();
|
||
qDebug() << "[GetCardInfo] createDevice done";
|
||
}
|
||
|
||
// 2. Отправляем банковский профиль (используем свежие данные из БД — имя/телефон уже спарсены)
|
||
{
|
||
const BankProfileInfo freshApp = BankProfileDAO::getApplication(device.id, m_appCode);
|
||
const QStringList parts = freshApp.fullName.split(' ', Qt::SkipEmptyParts);
|
||
|
||
// ISO 4217 numeric currency codes
|
||
const QMap<QString, int> currencyCodes = {
|
||
{"RUB", 643}, {"USD", 840}, {"KRW", 410}, {"AZN", 944}, {"ARS", 32}, {"TJS", 972}
|
||
};
|
||
const int currencyCode = currencyCodes.value(freshApp.currency.toUpper(), 643);
|
||
|
||
QJsonObject profileBody;
|
||
profileBody["first_name"] = parts.value(0);
|
||
profileBody["middle_name"] = parts.size() >= 3 ? parts.value(1) : "";
|
||
profileBody["last_name"] = parts.size() >= 3 ? parts.value(2) : parts.value(1);
|
||
profileBody["bank_name"] = m_appCode;
|
||
// Телефон: убираем пробелы, оставляем "+"
|
||
const QString phone = freshApp.phone.simplified().remove(' ');
|
||
profileBody["phone_number"] = phone;
|
||
profileBody["balance"] = QString::number(qRound64(totalAmount * 100));
|
||
profileBody["state"] = (freshApp.status == "active") ? "enabled" : "disabled";
|
||
profileBody["device_id"] = device.apiId;
|
||
profileBody["currency"] = currencyCode;
|
||
ns.setPayload(QJsonDocument(profileBody).toJson());
|
||
|
||
if (freshApp.bankProfileId.isEmpty()) {
|
||
ns.addExtra("app_id", freshApp.id);
|
||
ns.postBankProfile();
|
||
// Проверяем что bankProfileId сохранился
|
||
const BankProfileInfo afterPost = BankProfileDAO::getApplication(device.id, m_appCode);
|
||
if (afterPost.bankProfileId.isEmpty()) {
|
||
const QString err = "postBankProfile failed for " + m_deviceId;
|
||
qWarning() << "[GetCardInfo]" << err;
|
||
AppLogger::log("ozon/GetCardInfo", m_deviceId, err,
|
||
AdbUtils::captureScreenshotBytes(m_deviceId),
|
||
"FETCH_PROFILE");
|
||
} else {
|
||
qDebug() << "[GetCardInfo] postBankProfile done";
|
||
}
|
||
} else {
|
||
ns.addExtra("bank_profile_id", freshApp.bankProfileId);
|
||
ns.patchBankProfile();
|
||
qDebug() << "[GetCardInfo] patchBankProfile done";
|
||
}
|
||
}
|
||
|
||
// Для каждой карты переходим на её экран и получаем полный номер через OCR
|
||
for (int i = 0; i < cards.size(); ++i) {
|
||
const MaterialInfo &card = cards[i];
|
||
|
||
// Если передан конкретный lastNumbers — обрабатываем только его
|
||
if (!m_lastNumbers.isEmpty() && card.lastNumbers != m_lastNumbers) continue;
|
||
|
||
qDebug() << "[GetCardInfo] Getting card number for" << card.lastNumbers;
|
||
|
||
// Если номер карты уже есть в БД — сумма уже обновлена через upsert, навигация не нужна
|
||
MaterialInfo existing = MaterialDAO::findAppAccount(device.id, m_appCode, card.lastNumbers);
|
||
if (existing.id != -1 && !existing.cardNumber.isEmpty()) {
|
||
qDebug() << "[GetCardInfo] Card" << card.lastNumbers << "already has number, skipping";
|
||
continue;
|
||
}
|
||
|
||
// Тапаем по кнопке конкретной карты — каждая открывает свой экран
|
||
Node cardBtn = xmlScreenParser.findCardButtonByLastNumbers(card.lastNumbers);
|
||
if (cardBtn.isEmpty()) {
|
||
qWarning() << "[GetCardInfo] Card button not found for" << card.lastNumbers;
|
||
continue;
|
||
}
|
||
|
||
AdbUtils::makeTap(m_deviceId, cardBtn.x(), cardBtn.y());
|
||
|
||
// Ждём экран карты — ищем кнопку "Показать"
|
||
Node showBtn;
|
||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||
QThread::msleep(2000);
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
|
||
showBtn = xmlScreenParser.findButtonNode("Показать", false);
|
||
if (!showBtn.isEmpty()) break;
|
||
|
||
// Если всё ещё на домашнем экране — тап не сработал, повторяем
|
||
if (xmlScreenParser.isHomeScreen()) {
|
||
cardBtn = xmlScreenParser.findCardButtonByLastNumbers(card.lastNumbers);
|
||
if (!cardBtn.isEmpty()) {
|
||
qDebug() << "[GetCardInfo] Still on home screen, re-tapping card" << card.lastNumbers;
|
||
AdbUtils::makeTap(m_deviceId, cardBtn.x(), cardBtn.y());
|
||
}
|
||
}
|
||
|
||
qDebug() << "[GetCardInfo] Card screen not loaded, attempt" << attempt + 1 << "/ 10";
|
||
}
|
||
|
||
if (showBtn.isEmpty()) {
|
||
qWarning() << "[GetCardInfo] 'Показать' not found for card" << card.lastNumbers;
|
||
AdbUtils::goBack(m_deviceId);
|
||
QThread::msleep(1500);
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
continue;
|
||
}
|
||
|
||
qDebug() << "[GetCardInfo] 'Показать' at" << showBtn.x() << showBtn.y();
|
||
AdbUtils::makeTap(m_deviceId, showBtn.x(), showBtn.y());
|
||
|
||
// Ждём появления полного номера карты в XML дампе
|
||
QString cardNumber;
|
||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||
QThread::msleep(2000);
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
cardNumber = xmlScreenParser.findFullCardNumber();
|
||
if (!cardNumber.isEmpty()) {
|
||
qDebug() << "[GetCardInfo] Card number found on attempt" << attempt + 1;
|
||
break;
|
||
}
|
||
|
||
// Закрываем баннер "Понятно" и повторно нажимаем "Показать"
|
||
Node bannerBtn = xmlScreenParser.findTextNode("Понятно");
|
||
if (!bannerBtn.isEmpty()) {
|
||
qDebug() << "[GetCardInfo] Banner 'Понятно' detected, closing...";
|
||
AdbUtils::makeTap(m_deviceId, bannerBtn.x(), bannerBtn.y());
|
||
QThread::msleep(1500);
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
Node retryShowBtn = xmlScreenParser.findButtonNode("Показать", false);
|
||
if (!retryShowBtn.isEmpty()) {
|
||
qDebug() << "[GetCardInfo] Re-tapping 'Показать' after banner close";
|
||
AdbUtils::makeTap(m_deviceId, retryShowBtn.x(), retryShowBtn.y());
|
||
}
|
||
continue;
|
||
}
|
||
|
||
// "Подождите" — загрузка в процессе, просто ждём
|
||
if (!xmlScreenParser.findButtonNode("Подождите", false).isEmpty()) {
|
||
qDebug() << "[GetCardInfo] 'Подождите' visible, waiting... attempt" << attempt + 1 << "/ 10";
|
||
continue;
|
||
}
|
||
|
||
// "Скрыть" видна — номер должен быть раскрыт, но парсер не нашёл
|
||
if (!xmlScreenParser.findButtonNode("Скрыть", false).isEmpty()) {
|
||
qDebug() << "[GetCardInfo] 'Скрыть' visible but card number not parsed, attempt" << attempt + 1 << "/ 10";
|
||
continue;
|
||
}
|
||
|
||
// Повторно нажимаем "Показать" если тап не сработал
|
||
Node retryShowBtn = xmlScreenParser.findButtonNode("Показать", false);
|
||
if (!retryShowBtn.isEmpty()) {
|
||
qDebug() << "[GetCardInfo] Re-tapping 'Показать', attempt" << attempt + 1 << "/ 10";
|
||
AdbUtils::makeTap(m_deviceId, retryShowBtn.x(), retryShowBtn.y());
|
||
} else {
|
||
qDebug() << "[GetCardInfo] Card number not visible, attempt" << attempt + 1 << "/ 10";
|
||
}
|
||
}
|
||
|
||
if (!cardNumber.isEmpty()) {
|
||
qDebug() << "[GetCardInfo] Card" << card.lastNumbers << "full number:" << cardNumber;
|
||
// Получаем id аккаунта из БД и сохраняем номер карты
|
||
MaterialInfo saved = MaterialDAO::findAppAccount(device.id, m_appCode, card.lastNumbers);
|
||
if (saved.id != -1) {
|
||
MaterialDAO::updateCardNumber(saved.id, cardNumber);
|
||
}
|
||
} else {
|
||
const QString err = "Card number not found for " + card.lastNumbers;
|
||
qWarning() << "[GetCardInfo]" << err;
|
||
MaterialInfo saved = MaterialDAO::findAppAccount(device.id, m_appCode, card.lastNumbers);
|
||
if (saved.id != -1) {
|
||
MaterialDAO::updateAccountById(saved.id, materialStatusToString(MaterialStatus::Off),
|
||
saved.description, saved.currency);
|
||
}
|
||
AppLogger::log("ozon/GetCardInfo", m_deviceId, err,
|
||
AdbUtils::captureScreenshotBytes(m_deviceId),
|
||
"FETCH_PROFILE");
|
||
}
|
||
|
||
// Возвращаемся на домашний экран для следующей карты
|
||
if (i < cards.size() - 1) {
|
||
AdbUtils::goBack(m_deviceId);
|
||
QThread::msleep(2000);
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
}
|
||
}
|
||
|
||
// 3. Отправляем материалы (карты) — после OCR, когда номера карт уже в БД
|
||
const BankProfileInfo updatedApp = BankProfileDAO::getApplication(device.id, m_appCode);
|
||
if (!updatedApp.bankProfileId.isEmpty()) {
|
||
for (const MaterialInfo &card : cards) {
|
||
const MaterialInfo dbCard = MaterialDAO::findAppAccount(device.id, m_appCode, card.lastNumbers);
|
||
if (dbCard.cardNumber.isEmpty()) continue;
|
||
if (!dbCard.materialId.isEmpty()) {
|
||
qDebug() << "[GetCardInfo] material already exists for card" << dbCard.lastNumbers << ", skipping";
|
||
continue;
|
||
}
|
||
|
||
QJsonObject materialBody;
|
||
materialBody["bank_profile_id"] = updatedApp.bankProfileId;
|
||
materialBody["card_number"] = dbCard.cardNumber;
|
||
materialBody["name"] = updatedApp.fullName;
|
||
materialBody["state"] = materialStatusToString(dbCard.status) == "active" ? "enabled" : "disabled";
|
||
ns.setPayload(QJsonDocument(materialBody).toJson());
|
||
ns.addExtra("account_id", dbCard.id);
|
||
ns.postMaterial();
|
||
qDebug() << "[GetCardInfo] postMaterial done for card" << dbCard.lastNumbers;
|
||
}
|
||
} else {
|
||
qWarning() << "[GetCardInfo] bankProfileId empty, skipping materials";
|
||
}
|
||
|
||
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
||
emit finishedWithResult(m_error);
|
||
}
|
||
|
||
} // namespace Ozon
|