1
0
forked from BRT/arc
arc/android/dushanbe/GetCardInfoScript.cpp

315 lines
15 KiB
C++
Raw Permalink 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 <QThread>
#include <QJsonDocument>
#include <QJsonObject>
#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 Dushanbe {
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, app.appVersion};
TaskDumpRecorder::Scope dumpScope("dushanbe", "FETCH_CARDS", m_deviceId, dumpMeta, &m_error);
// Версионный трек приложения → версионно-зависимый парсинг (см. AppVersionTrack.h).
xmlScreenParser.setVersionTrack(Dushanbe::trackForVersion(app.appVersion));
qDebug() << "[Dushanbe::GetCardInfo] appVersion=" << app.appVersion
<< "→ track" << Dushanbe::trackName(xmlScreenParser.versionTrack());
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
closeGooglePlayBannerIfVisible(m_deviceId, device.screenWidth, device.screenHeight);
if (!xmlScreenParser.isHomeScreen()) {
qDebug() << "[Dushanbe::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);
}
// Парсим баланс с домашнего экрана
const double balance = xmlScreenParser.parseBalanceFromHomeScreen();
qDebug() << "[Dushanbe::GetCardInfo] balance:" << balance;
if (!BankProfileDAO::updateAmount(app.id, balance)) {
qWarning() << "[Dushanbe::GetCardInfo] Failed to update balance for app" << app.id;
}
// Тапаем "Карты" на домашнем экране
Node cardsButton = xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("Карты"));
if (cardsButton.x() == 0 && cardsButton.y() == 0) {
m_error = "Cannot find Cards button: " + m_deviceId;
qWarning() << m_error;
AdbUtils::tryToKillApplication(m_deviceId, app.package);
emit finishedWithResult(m_error);
return;
}
AdbUtils::makeTap(m_deviceId, cardsButton.x(), cardsButton.y());
QThread::msleep(3000);
// Ждём экран "Мои карты"
bool cardsScreenFound = 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.isCardsScreen()) {
cardsScreenFound = true;
break;
}
QThread::msleep(1000);
}
if (!cardsScreenFound) {
m_error = "Cards screen not detected: " + m_deviceId;
qWarning() << m_error;
AdbUtils::tryToKillApplication(m_deviceId, app.package);
emit finishedWithResult(m_error);
return;
}
// Парсим карты из списка (lastNumbers, баланс)
const QList<ScreenXmlParser::CardInfo> cards = xmlScreenParser.parseCards();
qDebug() << "[Dushanbe::GetCardInfo] Found" << cards.size() << "cards in list";
if (cards.isEmpty()) {
m_error = "No cards found: " + m_deviceId;
qWarning() << m_error;
AdbUtils::tryToKillApplication(m_deviceId, app.package);
emit finishedWithResult(m_error);
return;
}
// Находим все clickable ImageView с "****" — это карточки в списке
// Пропускаем кредитные карты
QList<Node> cardNodes;
for (const Node &node : xmlScreenParser.findAllNodes()) {
if (node.className == "android.widget.ImageView" && node.clickable &&
node.contentDesc.contains("****") &&
!node.contentDesc.contains(QString::fromUtf8("Кредитная карта"))) {
cardNodes.append(node);
}
}
// Кликаем по каждой карте для получения полного номера
for (int i = 0; i < cardNodes.size(); ++i) {
if (i >= cards.size()) break;
const Node &cardNode = cardNodes[i];
AdbUtils::makeTap(m_deviceId, cardNode.x(), cardNode.y());
QThread::msleep(2000);
// Ждём экран детали карты
bool detailFound = 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.isCardDetailScreen()) {
detailFound = true;
break;
}
QThread::msleep(1000);
}
if (!detailFound) {
qWarning() << "[Dushanbe::GetCardInfo] Card detail screen not found for card" << i;
continue;
}
// Ждём загрузки баланса (может быть 0 пока данные подтягиваются)
QThread::msleep(1500);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
closeGooglePlayBannerIfVisible(m_deviceId, device.screenWidth, device.screenHeight);
QString fullNumber = xmlScreenParser.parseFullCardNumber();
// Баланс берём с домашнего экрана (parseBalanceFromHomeScreen выше):
// в Душанбе все карты привязаны к одному счёту, и на экране детали
// карты баланс часто отображается как placeholder "0,00 TJS" пока
// данные подгружаются с сервера.
const double cardBalance = balance;
const QString holder = xmlScreenParser.parseCardDetailHolder();
const QString fullNumberClean = QString(fullNumber).remove(' ');
qDebug() << "[Dushanbe::GetCardInfo] Card" << i << ":" << fullNumber
<< "balance:" << cardBalance << "holder:" << holder;
// Без полного номера карты материал не создаём и на сервер не отправляем —
// защита от записей с пустым card_number (см. Ozon::GetCardInfo).
if (fullNumberClean.isEmpty()) {
qWarning() << "[Dushanbe::GetCardInfo] Full card number not parsed for card" << i
<< "(home-screen lastNumbers=" << cards[i].lastNumbers << ") — skipping";
AdbUtils::goBack(m_deviceId);
QThread::msleep(1500);
continue;
}
const QString lastNumbers = fullNumberClean.right(4);
// Достаём номер счёта: Настройки карты → Реквизиты карты → таб "Реквизиты счета".
// ВРЕМЕННО ОТКЛЮЧЕНО
QString accountNumber;
// const Node settingsBtn = xmlScreenParser.findCardSettingsButton();
// if (settingsBtn.x() > 0) {
// AdbUtils::makeTap(m_deviceId, settingsBtn.x(), settingsBtn.y());
// QThread::msleep(1500);
//
// bool sheetFound = false;
// for (int attempt = 0; attempt < 5; ++attempt) {
// xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
// if (xmlScreenParser.isCardSettingsBottomSheet()) {
// sheetFound = true;
// break;
// }
// QThread::msleep(1000);
// }
//
// if (sheetFound) {
// const Node requisitesItem = xmlScreenParser.findCardRequisitesMenuItem();
// if (requisitesItem.x() > 0) {
// AdbUtils::makeTap(m_deviceId, requisitesItem.x(), requisitesItem.y());
// QThread::msleep(1500);
//
// bool reqScreenFound = false;
// for (int attempt = 0; attempt < 5; ++attempt) {
// xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
// if (xmlScreenParser.isCardRequisitesScreen()) {
// reqScreenFound = true;
// break;
// }
// QThread::msleep(1000);
// }
//
// if (reqScreenFound) {
// const Node accountTab = xmlScreenParser.findAccountRequisitesTab();
// if (accountTab.x() > 0) {
// AdbUtils::makeTap(m_deviceId, accountTab.x(), accountTab.y());
// QThread::msleep(1500);
//
// // Несколько попыток — данные могут подгружаться
// for (int attempt = 0; attempt < 5; ++attempt) {
// xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
// accountNumber = xmlScreenParser.parseAccountNumber();
// if (!accountNumber.isEmpty()) break;
// QThread::msleep(1000);
// }
// qDebug() << "[Dushanbe::GetCardInfo] accountNumber for card" << i
// << ":" << accountNumber;
// } else {
// qWarning() << "[Dushanbe::GetCardInfo] Account tab not found for card" << i;
// }
// } else {
// qWarning() << "[Dushanbe::GetCardInfo] Requisites screen not detected for card" << i;
// }
// } else {
// qWarning() << "[Dushanbe::GetCardInfo] 'Реквизиты карты' menu item not found for card" << i;
// }
// } else {
// qWarning() << "[Dushanbe::GetCardInfo] Card settings bottom sheet not detected for card" << i;
// }
//
// // Возврат на экран детали карты — bottom sheet и/или экран Реквизитов
// for (int attempt = 0; attempt < 4; ++attempt) {
// xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
// if (xmlScreenParser.isCardDetailScreen()) break;
// AdbUtils::goBack(m_deviceId);
// QThread::msleep(1200);
// }
// } else {
// qWarning() << "[Dushanbe::GetCardInfo] 'Настройки карты' button not found for card" << i;
// }
MaterialInfo account;
account.deviceId = device.id;
account.appCode = m_appCode;
account.lastNumbers = lastNumbers;
account.cardNumber = QString(fullNumber).remove(' ');
account.accountNumber = accountNumber;
account.amount = cardBalance;
account.currency = "TJS";
account.description = cards[i].description.isEmpty() ? holder : cards[i].description;
account.status = MaterialStatus::Off;
account.updateTime = QDateTime::currentDateTimeUtc();
qDebug() << "[Dushanbe::GetCardInfo] Saving card:" << "lastNumbers=" << lastNumbers
<< "cardNumber=" << account.cardNumber << "amount=" << cardBalance;
if (!MaterialDAO::upsertAccount(account)) {
qWarning() << "[Dushanbe::GetCardInfo] Failed to upsert account" << lastNumbers;
}
// Отправляем на сервер: инвариант — только active-карта и только если
// другая карта в (deviceId, appCode) ещё не синхронизирована.
const BankProfileInfo updatedApp = BankProfileDAO::getApplication(device.id, m_appCode);
if (!updatedApp.bankProfileId.isEmpty()) {
const MaterialInfo savedAccount = MaterialDAO::findAppAccount(device.id, m_appCode, lastNumbers);
if (savedAccount.id != -1
&& savedAccount.status == MaterialStatus::Active
&& savedAccount.materialId.isEmpty()) {
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() << "[Dushanbe::GetCardInfo] another material already synced, skipping POST for" << lastNumbers;
} else {
NetworkService ns;
ns.setDeviceId(m_deviceId);
ns.setPayload(buildMaterialPayload(savedAccount, updatedApp));
ns.addExtra("account_id", savedAccount.id);
ns.postMaterial();
qDebug() << "[Dushanbe::GetCardInfo] postMaterial done for" << lastNumbers;
}
}
}
// Назад к списку карт
AdbUtils::goBack(m_deviceId);
QThread::msleep(1500);
// Ждём возврата на экран "Мои карты"
for (int attempt = 0; attempt < 5; ++attempt) {
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
closeGooglePlayBannerIfVisible(m_deviceId, device.screenWidth, device.screenHeight);
if (xmlScreenParser.isCardsScreen()) break;
QThread::msleep(1000);
}
}
AdbUtils::tryToKillApplication(m_deviceId, app.package);
emit finishedWithResult(m_error);
}
} // namespace Dushanbe