#include "GetLastTransactionsScript.h" #include #include #include "adb/AdbUtils.h" #include "dao/MaterialDAO.h" #include "dao/BankProfileDAO.h" #include "dao/DeviceDAO.h" #include "dao/TransactionDAO.h" #include "AppLogger.h" namespace Dushanbe { GetLastTransactionsScript::GetLastTransactionsScript( QString deviceId, QString appCode, double searchAmount, QDateTime searchTime, QObject *parent ) : CommonScript(parent), m_deviceId(std::move(deviceId)), m_appCode(std::move(appCode)), m_searchAmount(searchAmount), m_searchTime(std::move(searchTime)) { } GetLastTransactionsScript::~GetLastTransactionsScript() = default; void GetLastTransactionsScript::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)); if (!xmlScreenParser.isHomeScreen()) { qDebug() << "[Dushanbe::GetLastTransactions] 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("dushanbe/GetLastTransactions", m_deviceId, m_error, AdbUtils::captureScreenshotBytes(m_deviceId), "FETCH_TRANSACTIONS"); AdbUtils::tryToKillApplication(m_deviceId, app.package); emit finishedWithResult(m_error); return; } xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); } // Тапаем "История" в нижнем меню Node historyBtn = xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("История")); if (historyBtn.x() == 0 && historyBtn.y() == 0) { m_error = "Cannot find History button: " + m_deviceId; qWarning() << m_error; AdbUtils::tryToKillApplication(m_deviceId, app.package); emit finishedWithResult(m_error); return; } AdbUtils::makeTap(m_deviceId, historyBtn.x(), historyBtn.y()); QThread::msleep(3000); // Ждём экран истории bool historyLoaded = false; for (int attempt = 0; attempt < 10; ++attempt) { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); if (xmlScreenParser.isHistoryScreen()) { historyLoaded = true; break; } QThread::msleep(1500); } if (!historyLoaded) { m_error = "History screen not loaded: " + m_deviceId; qWarning() << m_error; AdbUtils::tryToKillApplication(m_deviceId, app.package); emit finishedWithResult(m_error); return; } // Находим accountId int accountId = -1; const QList accounts = MaterialDAO::getAccounts(device.id, m_appCode); if (!accounts.isEmpty()) { accountId = accounts.first().id; } else { qWarning() << "[Dushanbe::GetLastTransactions] No account found"; } qDebug() << "[Dushanbe::GetLastTransactions] Searching: amount=" << m_searchAmount << "time=" << m_searchTime.toString(Qt::ISODate); const QTimeZone tjTz("Asia/Dushanbe"); // UTC+5 const int maxScrolls = 10; for (int scroll = 0; scroll <= maxScrolls; ++scroll) { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); const QList items = xmlScreenParser.parseTransactionItems(); for (const auto &tx : items) { if (qAbs(tx.amount - m_searchAmount) > 0.01) continue; qDebug() << "[Dushanbe::GetLastTransactions] Amount match:" << tx.amount << "phone:" << tx.phone << "time:" << tx.time; // Нашли по сумме — тапаем для получения деталей // Ищем соответствующий node чтобы по нему тапнуть Node txNode; for (const Node &node : xmlScreenParser.findAllNodes()) { if (node.className != "android.widget.ImageView" || !node.clickable) continue; if (!node.contentDesc.contains("***")) continue; // Проверяем что содержит нужную сумму QString amountStr = QString::number(tx.amount, 'f', 2); if (node.contentDesc.contains(amountStr)) { txNode = node; break; } } if (txNode.x() == 0 && txNode.y() == 0) { qWarning() << "[Dushanbe::GetLastTransactions] Cannot find node for transaction"; continue; } AdbUtils::makeTap(m_deviceId, txNode.x(), txNode.y()); QThread::msleep(2000); // Ждём экран деталей bool detailLoaded = false; for (int attempt = 0; attempt < 10; ++attempt) { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); if (xmlScreenParser.isTransactionDetailScreen()) { detailLoaded = true; break; } QThread::msleep(1500); } if (!detailLoaded) { qWarning() << "[Dushanbe::GetLastTransactions] Detail screen not loaded"; AdbUtils::goBack(m_deviceId); QThread::msleep(1000); continue; } const auto detail = xmlScreenParser.parseTransactionDetail(); // Проверяем время ±1 час if (m_searchTime.isValid() && !detail.date.isEmpty() && !detail.time.isEmpty()) { QDateTime txTime = QDateTime::fromString( detail.date + " " + detail.time, "dd.MM.yyyy HH:mm:ss"); if (!txTime.isValid()) { txTime = QDateTime::fromString(detail.date + " " + detail.time, "dd.MM.yyyy HH:mm"); } if (txTime.isValid()) { txTime.setTimeZone(tjTz); const qint64 diffSecs = qAbs(txTime.toUTC().secsTo(m_searchTime.toUTC())); if (diffSecs > 3600) { qDebug() << "[Dushanbe::GetLastTransactions] Time mismatch: diff=" << diffSecs << "sec, skipping"; AdbUtils::goBack(m_deviceId); QThread::msleep(1000); continue; } } } // Нашли! Сохраняем qDebug() << "[Dushanbe::GetLastTransactions] FOUND:" << "amount=" << detail.amount << "date=" << detail.date << detail.time << "opId=" << detail.operationId << "receiver=" << detail.receiverAccount << "status=" << detail.status; if (accountId != -1) { TransactionInfo saveTx; saveTx.accountId = accountId; saveTx.amount = detail.amount; saveTx.fee = detail.fee; saveTx.bankName = m_appCode; saveTx.bankTime = detail.date + " " + detail.time; saveTx.bankTrExternalId = detail.operationId; saveTx.description = detail.provider; saveTx.phone = detail.receiverAccount; saveTx.name = detail.receiverAccount; saveTx.status = (detail.status == QString::fromUtf8("Успешный")) ? TransactionStatus::Complete : TransactionStatus::Unknown; saveTx.type = TransactionType::Phone; saveTx.timestamp = QDateTime::currentDateTimeUtc(); QDateTime txTime = QDateTime::fromString( detail.date + " " + detail.time, "dd.MM.yyyy HH:mm:ss"); if (txTime.isValid()) { txTime.setTimeZone(tjTz); saveTx.completeTime = txTime; } TransactionDAO::insertTransactionIfNotExists(saveTx); } // Назад к истории Node nazadBtn = xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("Назад")); if (nazadBtn.x() > 0 && nazadBtn.y() > 0) { AdbUtils::makeTap(m_deviceId, nazadBtn.x(), nazadBtn.y()); } else { AdbUtils::goBack(m_deviceId); } QThread::msleep(1000); emit finishedWithResult(m_error); // m_error пустой — успех return; } // Скроллим вниз if (scroll < maxScrolls) { const int x = device.screenWidth / 2; const int fromY = device.screenHeight * 3 / 4; const int toY = device.screenHeight / 4; AdbUtils::makeSwipe(m_deviceId, x, fromY, x, toY); QThread::msleep(2000); } } m_error = "Transaction not found: amount=" + QString::number(m_searchAmount, 'f', 2) + " time=" + m_searchTime.toString(Qt::ISODate); qWarning() << "[Dushanbe::GetLastTransactions]" << m_error; // Возвращаемся на главную Node glavnayaBtn = xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("Главная")); if (glavnayaBtn.x() > 0 && glavnayaBtn.y() > 0) { AdbUtils::makeTap(m_deviceId, glavnayaBtn.x(), glavnayaBtn.y()); } else { AdbUtils::goBack(m_deviceId); } QThread::msleep(1000); emit finishedWithResult(m_error); } } // namespace Dushanbe