#include "GetCardInfoScript.h" #include #include #include #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}; TaskDumpRecorder::Scope dumpScope("dushanbe", "FETCH_CARDS", m_deviceId, dumpMeta, &m_error); 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 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 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