178 lines
7.4 KiB
C++
178 lines
7.4 KiB
C++
#include "GetProfileInfoScript.h"
|
||
|
||
#include <optional>
|
||
|
||
#include <QElapsedTimer>
|
||
#include <QJsonDocument>
|
||
#include <QJsonObject>
|
||
#include <QRegularExpression>
|
||
#include <QThread>
|
||
|
||
#include "adb/AdbUtils.h"
|
||
#include "dao/MaterialDAO.h"
|
||
#include "dao/BankProfileDAO.h"
|
||
#include "dao/DeviceDAO.h"
|
||
#include "dump/TaskDumpRecorder.h"
|
||
#include "net/NetworkService.h"
|
||
|
||
namespace Dushanbe {
|
||
|
||
GetProfileInfoScript::GetProfileInfoScript(
|
||
QString deviceId,
|
||
QString appCode,
|
||
QObject *parent
|
||
) : CommonScript(parent),
|
||
m_deviceId(std::move(deviceId)),
|
||
m_appCode(std::move(appCode)) {
|
||
}
|
||
|
||
GetProfileInfoScript::~GetProfileInfoScript() = default;
|
||
|
||
void GetProfileInfoScript::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};
|
||
TaskDumpRecorder::Scope dumpScope("dushanbe", "FETCH_PROFILE_INFO", m_deviceId, dumpMeta, &m_error);
|
||
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
|
||
if (!xmlScreenParser.isHomeScreen()) {
|
||
qDebug() << "[Dushanbe::GetProfileInfo] 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));
|
||
}
|
||
|
||
// Всегда тапаем кнопку обновления — баланс на экране может быть закэширован
|
||
// и не отражать актуальное состояние счёта. Лоадера у refresh нет, но во
|
||
// время загрузки родительский блок временно становится content-desc="TJS"
|
||
// (без даты, без числа) — это надёжный маркер «идёт обновление». Поэтому
|
||
// после тапа поллим экран до появления распарсиваемого баланса.
|
||
Node refreshBtn = xmlScreenParser.findRefreshButtonOnHomeScreen();
|
||
std::optional<double> parsedOpt;
|
||
if (refreshBtn.x() > 0) {
|
||
qDebug() << "[Dushanbe::GetProfileInfo] Tapping refresh to get up-to-date balance";
|
||
AdbUtils::makeTap(m_deviceId, refreshBtn.x(), refreshBtn.y());
|
||
|
||
constexpr int kPollIntervalMs = 500;
|
||
constexpr int kPollTimeoutMs = 15000;
|
||
QElapsedTimer t;
|
||
t.start();
|
||
while (true) {
|
||
QThread::msleep(kPollIntervalMs);
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
parsedOpt = xmlScreenParser.parseBalanceFromHomeScreenOpt();
|
||
if (parsedOpt.has_value()) break;
|
||
if (t.elapsed() >= kPollTimeoutMs) break;
|
||
}
|
||
|
||
if (!parsedOpt.has_value()) {
|
||
m_error = "Balance did not load after refresh: " + m_deviceId + " " + m_appCode;
|
||
qWarning() << m_error;
|
||
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
||
emit finishedWithResult(m_error);
|
||
return;
|
||
}
|
||
} else {
|
||
qWarning() << "[Dushanbe::GetProfileInfo] Refresh button not found, parsing as-is";
|
||
parsedOpt = xmlScreenParser.parseBalanceFromHomeScreenOpt();
|
||
}
|
||
|
||
const double parsedBalance = parsedOpt.value_or(0.0);
|
||
qDebug() << "[Dushanbe::GetProfileInfo] Parsed balance after refresh:" << parsedBalance;
|
||
// Нулевой баланс — валидное состояние счёта (пустой кошелёк), не ошибка.
|
||
// Failure-ветка выше (Balance did not load) ловит реальную проблему —
|
||
// зависший loading, когда блок "TJS\n<дата>" так и не появился.
|
||
BankProfileDAO::updateAmount(app.id, parsedBalance);
|
||
// У Душанбе все карты сидят на одном счёте, а экран деталей карты
|
||
// отдаёт placeholder "0,00 TJS" — единственный источник правды
|
||
// для materials.amount это баланс home-screen.
|
||
MaterialDAO::setAmountByApp(device.id, m_appCode, parsedBalance);
|
||
|
||
// Тапаем по кнопке меню (гамбургер, левый верхний угол)
|
||
// Это первый clickable ImageView в верхней панели
|
||
Node menuButton;
|
||
for (const Node &node : xmlScreenParser.findAllNodes()) {
|
||
if (node.className == "android.widget.ImageView" && node.clickable && node.y() < 300) {
|
||
menuButton = node;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (menuButton.x() == 0 && menuButton.y() == 0) {
|
||
m_error = "Cannot find menu button: " + m_deviceId;
|
||
qWarning() << m_error;
|
||
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
||
emit finishedWithResult(m_error);
|
||
return;
|
||
}
|
||
|
||
AdbUtils::makeTap(m_deviceId, menuButton.x(), menuButton.y());
|
||
QThread::msleep(2000);
|
||
|
||
// Ждём боковое меню
|
||
bool sideMenuFound = 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.isSideMenu()) {
|
||
sideMenuFound = true;
|
||
break;
|
||
}
|
||
QThread::msleep(1000);
|
||
}
|
||
|
||
if (!sideMenuFound) {
|
||
m_error = "Side menu not detected: " + m_deviceId;
|
||
qWarning() << m_error;
|
||
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
||
emit finishedWithResult(m_error);
|
||
return;
|
||
}
|
||
|
||
// Парсим ФИО и телефон из бокового меню
|
||
const QString fullName = xmlScreenParser.parseProfileFullName();
|
||
QString phone = xmlScreenParser.parseProfilePhone();
|
||
phone.remove(QRegularExpression("[^0-9+]"));
|
||
qDebug() << "[Dushanbe::GetProfileInfo] fullName:" << fullName << "phone:" << phone;
|
||
|
||
if (!fullName.isEmpty()) {
|
||
BankProfileDAO::updateProfileInfo(app.id, fullName, phone, "");
|
||
}
|
||
|
||
if (const QString err = createBankProfileIfNeeded(device.id, m_appCode, parsedBalance); !err.isEmpty()) {
|
||
m_error = err;
|
||
emit finishedWithResult(m_error);
|
||
return;
|
||
}
|
||
|
||
const QList<MaterialInfo> allAccounts = MaterialDAO::getAccounts(device.id, m_appCode);
|
||
QList<QPair<MaterialInfo, Node>> accountsToPost;
|
||
for (const auto &acc : allAccounts) {
|
||
if (acc.materialId.isEmpty()) {
|
||
accountsToPost.append({acc, Node()});
|
||
}
|
||
}
|
||
if (!accountsToPost.isEmpty()) {
|
||
postAccountsData(accountsToPost);
|
||
}
|
||
|
||
emit finishedWithResult(m_error);
|
||
}
|
||
|
||
} // namespace Dushanbe
|