1
0
forked from BRT/arc
arc/android/ozon/GetCardInfoScript.cpp
trnsmkot 8bc198a787 Add SettingsDAO implementation, schema migration, and related updates
Implemented `SettingsDAO` with `get`, `set`, `remove`, and related methods for key-value storage. Added database schema migration for a new `settings` table. Updated `EventDAO` with error handling improvements and `AbstractDAO` with helper utilities. Enhanced Ozon scripts with ad banner detection and handling.
2026-03-09 15:53:35 +07:00

331 lines
15 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "GetCardInfoScript.h"
#include <QDateTime>
#include <QJsonDocument>
#include <QJsonObject>
#include <QSettings>
#include <QThread>
#include "adb/AdbUtils.h"
#include "dao/AccountDAO.h"
#include "dao/ApplicationDAO.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() {
const DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId);
const ApplicationInfo app = ApplicationDAO::getApplication(m_deviceId, 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(device.id, 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;
ApplicationDAO::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";
}
// Ждём пока все карты подгрузятся — до 5 попыток
QList<AccountInfo> 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 (AccountInfo &card : cards) {
card.deviceId = m_deviceId;
card.appCode = m_appCode;
card.updateTime = QDateTime::currentDateTimeUtc();
card.status = AccountStatus::Active;
if (!AccountDAO::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) {
ApplicationDAO::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 ApplicationInfo freshApp = ApplicationDAO::getApplication(m_deviceId, 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}
};
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["currency"] = currencyCode;
ns.setPayload(QJsonDocument(profileBody).toJson());
if (freshApp.bankProfileId.isEmpty()) {
ns.addExtra("app_id", freshApp.id);
ns.postBankProfile();
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 AccountInfo &card = cards[i];
// Если передан конкретный lastNumbers — обрабатываем только его
if (!m_lastNumbers.isEmpty() && card.lastNumbers != m_lastNumbers) continue;
qDebug() << "[GetCardInfo] Getting card number for" << card.lastNumbers;
// Если номер карты уже есть в БД — сумма уже обновлена через upsert, навигация не нужна
AccountInfo existing = AccountDAO::findAppAccount(m_deviceId, 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());
// Ждём экран карты — ищем "Реквизиты карты" в XML дампе
Node showBtn;
for (int attempt = 0; attempt < 10; ++attempt) {
QThread::msleep(2000);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
Node reqNode = xmlScreenParser.findTextNode("Реквизиты карты");
if (!reqNode.isEmpty()) {
showBtn = xmlScreenParser.findButtonNode("Показать", false);
if (!showBtn.isEmpty()) break;
}
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;
}
qDebug() << "[GetCardInfo] Card number not visible, attempt" << attempt + 1 << "/ 10";
}
if (!cardNumber.isEmpty()) {
qDebug() << "[GetCardInfo] Card" << card.lastNumbers << "full number:" << cardNumber;
// Получаем id аккаунта из БД и сохраняем номер карты
AccountInfo saved = AccountDAO::findAppAccount(m_deviceId, m_appCode, card.lastNumbers);
if (saved.id != -1) {
AccountDAO::updateCardNumber(saved.id, cardNumber);
}
} else {
qWarning() << "[GetCardInfo] Card number not found for" << card.lastNumbers;
}
// Возвращаемся на домашний экран для следующей карты
if (i < cards.size() - 1) {
AdbUtils::goBack(m_deviceId);
QThread::msleep(2000);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
}
}
// 3. Отправляем материалы (карты) — после OCR, когда номера карт уже в БД
const ApplicationInfo updatedApp = ApplicationDAO::getApplication(m_deviceId, m_appCode);
if (!updatedApp.bankProfileId.isEmpty()) {
for (const AccountInfo &card : cards) {
const AccountInfo dbCard = AccountDAO::findAppAccount(m_deviceId, 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;
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