#include "HomeScreenScript.h" #include #include #include #include #include "ScreenXmlParser.h" #include "adb/AdbUtils.h" HomeScreenScript::HomeScreenScript(QObject *parent) : QObject(parent) { } HomeScreenScript::~HomeScreenScript() = default; bool HomeScreenScript::goToHomeScreen( const QString &deviceId, const QString &packageName, const QString &pinCode, const int width, const int height ) { // Запустить устройство if (const QString pid = AdbUtils::tryToGetRunningAppPid(deviceId, packageName); !pid.isEmpty()) { qDebug() << "Process already running, pid: " << pid; AdbUtils::getScreenDumpAsXml(deviceId, 1000); AdbUtils::tryToKillApplication(deviceId, packageName); } if (!AdbUtils::tryToStartApplication(deviceId, packageName)) { qWarning() << "Process has not started"; return false; } xmlScreenParser.parseAndSaveXml( AdbUtils::getScreenDumpAsXml(deviceId, 0) ); // Даем минимальное время на запуск QThread::msleep(4500); int counter = 0; int restarted = 0; int totalCounter = 0; bool findAndInputPincode = false; while (true) { counter++; totalCounter++; // 3 раза уже перезапускали приложение, завершаем попытки if (restarted > 2) { return false; } // Если более 30 секунд мы пытаемся перейти на домашнюю, перезапускаем if (counter > 30) { AdbUtils::tryToKillApplication(deviceId, packageName); QThread::msleep(4500); ++restarted; counter = 0; } const QString xml = AdbUtils::getScreenDumpAsXml(deviceId, totalCounter + 1); xmlScreenParser.parseAndSaveXml(xml); qDebug() << "Created xml+screenshot, idx: " << (totalCounter + 1); if (xmlScreenParser.isHomeScreen()) { qDebug() << "isHomeScreen!!!!!!!"; return true; } // Проверяем на банеры после пин-кодом Node closeBannerOrDialogNode = xmlScreenParser.tryToFindBannerOrDialog(width, height); if (closeBannerOrDialogNode.x() != -1 && closeBannerOrDialogNode.y() != -1) { qDebug() << "--- tryToFindBannerOrDialog"; AdbUtils::makeTap(deviceId, closeBannerOrDialogNode.x(), closeBannerOrDialogNode.y()); QThread::msleep(1000); continue; } // Проверяем на банеры перед пин-кодом Node closeBannerNode = xmlScreenParser.tryToFindSecurityBanner(); if (!closeBannerNode.isEmpty()) { qDebug() << "--- tryToFindSecurityBanner"; AdbUtils::makeTap(deviceId, closeBannerNode.x(), closeBannerNode.y()); QThread::msleep(1000); continue; } // Проверяем, что это экран ввода пин-кода if (!findAndInputPincode) { if (const QList pincode = xmlScreenParser.tryToFindPinCode(pinCode, "Введите ПИН"); pincode.size() == 4) { qDebug() << "--- tryToFindPinCode"; // делаем 4 тапа и ждем секунду и продолжаем for (const Node &node: pincode) { AdbUtils::makeTap(deviceId, node.x(), node.y()); QThread::msleep(QRandomGenerator::global()->bounded(200, 901)); } // После ввода пин-код нужно сделать задержку, пока подгрузка идет, чтобы снова не спарсить findAndInputPincode = true; QThread::msleep(3000); continue; } } QThread::msleep(1000); // Если что-то пойдет не по плану... if (totalCounter > 100) { return false; } } } QString getPriceAsString(const double value) { QString sign = value < 0 ? "Минус" : "Плюс"; const auto intValue = static_cast(std::abs(value)); // округление до целого QString number = QString::number(intValue); QString formatted; int count = 0; for (int i = number.length() - 1; i >= 0; --i) { formatted.prepend(number[i]); ++count; if (count % 3 == 0 && i != 0) { formatted.prepend(' '); } } return QString("%1 %2").arg(sign, formatted); } Node swipeToButton( ScreenXmlParser &xmlScreenParser, const QString &deviceId, const QString &text, int &counter, const int width, const int height ) { for (int i = 0; i < 10; ++i) { if (Node button = xmlScreenParser.findButtonNode(text, true); button.x() > 0 && button.y() > 0) { return button; } const int x = width / 2; const int offset = height / 4; AdbUtils::makeSwipe(deviceId, x, height - offset, x, offset); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter)); } return {}; } void HomeScreenScript::start() { // Достаем данные из БД по ИД транзакции, перед тем как запустить мы кладем в БД все данные const QString deviceId = "6edc4a47"; const QString packageName = "ru.rshb.dbo"; const QString pinCode = "1102"; const int width = 720; const int height = 1600; const QString cardNumber = "7 1 8 3"; // В том числе и данные на каком устройстве это делать // TODO тут код работы с БД // +7 (964) 181-51-46 const QString phoneNumber = "+79641815146"; const QString bankName = "Альфа-Банк"; const QString paySum = "10"; int index = 0; xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++index)); // xmlScreenParser.test(); // тут просто получаем инфу которую спарсил // == 1. Входящий (TransactionType::Phone) // Перевод через СБП от Егор Иванович Ж (+7(000)-000-00-87) из Озон Банк (Ozon) на 40817810328000022077 . // Идентификатор операции в СБП С2С B5121111443333300170011491301 , Дата операции 01.05.2025 14:14:41 Статус перевода Исполнен // == 2. Исходящий (TransactionType::Phone) // Перевод Надежда Иванкина К** в Сбербанк через СБП, по номеру телефона +79000810046, ID перевода 445900141, дата 03.05.2025 // (TransactionType::Card) -> T-BANK CARD2CARD // TransactionInfo trInfo = xmlScreenParser.parseTransactionInfoHistory(width, height); // qDebug().noquote().nospace() << convertTransactionToString(trInfo); // Если начинается с ЕСПП - то не кликаем еще, ждет обработку // А все переводы по карте - они не начинаются со слова "Перевод" -> // .. пробуем кликать, но если не кликается ??? QList transactions = xmlScreenParser.parseTodayAndYesterdayHistory(); for (const TransactionInfo &info: transactions) { QString text = QString("%1 %2").arg(info.description, getPriceAsString(info.amount)); qDebug().noquote().nospace() << convertTransactionToString(info); // Node trButton = xmlScreenParser.findButtonNode(text, true); Node trButton = swipeToButton(xmlScreenParser, deviceId, text, index, width, height); if (!trButton.isEmpty()) { AdbUtils::makeTap(deviceId, trButton.x(), trButton.y()); QThread::msleep(500); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++index)); Node moreDetailBtn = xmlScreenParser.findButtonNode("Детали операции", false); if (!moreDetailBtn.isEmpty()) { AdbUtils::makeTap(deviceId, moreDetailBtn.x(), moreDetailBtn.y()); QThread::msleep(200); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++index)); TransactionInfo info = xmlScreenParser.parseTransactionInfoHistory(width, height); qDebug().noquote().nospace() << convertTransactionToString(info); } AdbUtils::goBack(deviceId); } else { qWarning() << "--- trButton not found.........."; } // Перевод ВЛАДИСЛАВ НИКОЛАЕВИЧ К** в Т-Банк через СБП, по номеру телефона +7985553... Минус 10 рублей 0 копеек } return; int payCounter = 0; if (false) { // Оплата со страницы карты xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); Node payBtn = xmlScreenParser.findButtonNode("Оплатить", false); if (!payBtn.isEmpty()) { AdbUtils::makeTap(deviceId, payBtn.x(), payBtn.y()); QThread::msleep(1000); } else { qWarning() << "--- payBtn not found"; } xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); // TODO - По номеру карты В РСХБ и другие банки Node payTitle = xmlScreenParser.findTextNode("Платежи и переводы"); if (!payTitle.isEmpty() && payTitle.y() < 300) { Node payByPhoneBtn = xmlScreenParser.findButtonNode("По номеру телефона В РСХБ и через", false); if (!payByPhoneBtn.isEmpty()) { AdbUtils::makeTap(deviceId, payByPhoneBtn.x(), payByPhoneBtn.y()); QThread::msleep(1000); } else { qWarning() << "--- payByPhoneBtn not found"; } } else { qWarning() << "--- payTitle not found"; } xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); Node payByPhoneTitle = xmlScreenParser.findTextNode("Перевод по телефону"); Node continueBtn = xmlScreenParser.findButtonNode("Продолжить", false); if (!payByPhoneTitle.isEmpty() && payByPhoneTitle.y() < 300 && continueBtn.isEmpty()) { Node inputPhone = xmlScreenParser.findFirstEditText(); AdbUtils::makeTap(deviceId, inputPhone.x(), inputPhone.y()); QThread::msleep(500); AdbUtils::inputText(deviceId, phoneNumber); QThread::msleep(1000); } else { qWarning() << "--- payByPhoneTitle not found (no continueBtn)"; } xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); QString payBankName; payBankName = bankName == "Россельхозбанк" ? "Россельхозбанк" : "Другой банк через СБП"; Node payOtherBankBtn = xmlScreenParser.findButtonNode(payBankName, false); if (!payOtherBankBtn.isEmpty()) { AdbUtils::makeTap(deviceId, payOtherBankBtn.x(), payOtherBankBtn.y()); QThread::msleep(1000); } else { qWarning() << "--- payOtherBankBtn not found"; } xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); for (int i = 0; i < 5; ++i) { Node allBanksTitle = xmlScreenParser.findTextNode("Банки участники"); if (!allBanksTitle.isEmpty() && allBanksTitle.y() < 300) { Node inputBankName = xmlScreenParser.findFirstEditText(); AdbUtils::makeTap(deviceId, inputBankName.x(), inputBankName.y()); QThread::msleep(500); AdbUtils::enableAdbIMEKeyboard(deviceId); AdbUtils::inputAdbIMEText(deviceId, bankName); AdbUtils::disableAdbIMEKeyboard(deviceId); QThread::msleep(1000); break; } else { qWarning() << "--- allBanksTitle not found, i: " << i; } QThread::msleep(1000); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); } xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); for (int i = 0; i < 5; ++i) { Node bankNameListItem = xmlScreenParser.findTextNode(bankName); if (!bankNameListItem.isEmpty()) { AdbUtils::makeTap(deviceId, bankNameListItem.x(), bankNameListItem.y()); QThread::msleep(1000); break; } else { qWarning() << "--- bankNameListItem not found, i: " << i; } QThread::msleep(1000); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); } xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); Node payByPhoneScreenTitle = xmlScreenParser.findTextNode("Перевод по телефону"); Node continuePayBtn = xmlScreenParser.findButtonNode("Продолжить", false); if (!payByPhoneScreenTitle.isEmpty() && payByPhoneScreenTitle.y() < 300 && !continuePayBtn.isEmpty()) { Node sumBtn = xmlScreenParser.findButtonNode("0", false); if (!sumBtn.isEmpty()) { AdbUtils::makeTap(deviceId, sumBtn.x(), sumBtn.y()); QThread::msleep(500); AdbUtils::inputText(deviceId, paySum); QThread::msleep(500); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); Node readyBtn = xmlScreenParser.findTextNode("Готово"); if (!readyBtn.isEmpty()) { AdbUtils::makeTap(deviceId, readyBtn.x(), readyBtn.y()); QThread::msleep(500); AdbUtils::makeTap(deviceId, continuePayBtn.x(), continuePayBtn.y()); QThread::msleep(1000); } else { qWarning() << "--- readyBtn not found"; } } else { qWarning() << "--- sumBtn not found"; } } else { qWarning() << "--- payByPhoneScreenTitle not found"; } xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); for (int i = 0; i < 5; ++i) { // TODO кнопка содержит "Перевести 10 ₽" если будет от 1000 то скорее всвего будет "Перевести 1 000 ₽", // TODO не ясно как форматирование будет, поэтому пока на начало зашьем Node makePayBtn = xmlScreenParser.findButtonNode("Перевести ", true); if (!makePayBtn.isEmpty()) { AdbUtils::makeTap(deviceId, makePayBtn.x(), makePayBtn.y()); QThread::msleep(500); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); if (const QList pincode = xmlScreenParser.tryToFindPinCode(pinCode, "Введите код доступа"); pincode.size() == 4) { // FIXME вынести в отдельный метод // делаем 4 тапа и ждем секунду и продолжаем for (const Node &node: pincode) { AdbUtils::makeTap(deviceId, node.x(), node.y()); QThread::msleep(QRandomGenerator::global()->bounded(200, 901)); } // После ввода пин-кода, ждем 3с QThread::msleep(3000); } break; } else { qWarning() << "--- makePayBtn not found, i: " << i; } QThread::msleep(1000); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); } } xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); for (int i = 0; i < 5; ++i) { Node moreDetailBtn = xmlScreenParser.findButtonNode("Детали операции", false); if (!moreDetailBtn.isEmpty()) { AdbUtils::makeTap(deviceId, moreDetailBtn.x(), moreDetailBtn.y()); QThread::msleep(200); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); Node paymentCompleted = xmlScreenParser.findTextNode("Исполнен", 0, 0, width, height / 2); Node paymentInProgress = xmlScreenParser.findTextNode("Выполняется", 0, 0, width, height / 2); if (!paymentCompleted.isEmpty()) { // Если вдруг исполнен } else if (!paymentInProgress.isEmpty()) { // Если все еще в ожидании } else { qWarning() << "--- paymentCompleted or paymentInProgress not found..."; } break; } else { qWarning() << "--- moreDetailBtn not found, i: " << i; } QThread::msleep(1000); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter)); } return; int counter = 0; xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, counter)); Node closeBannerOrDialogNode = xmlScreenParser.tryToFindBannerOrDialog(width, height); if (closeBannerOrDialogNode.x() != -1 && closeBannerOrDialogNode.y() != -1) { qDebug() << "--- tryToFindBannerOrDialog"; AdbUtils::makeTap(deviceId, closeBannerOrDialogNode.x(), closeBannerOrDialogNode.y()); QThread::msleep(1000); } xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter)); if (xmlScreenParser.isHomeScreen()) { qDebug() << "--- isHomeScreen"; if (Node allCardTitle = xmlScreenParser.findTextNode("Счета и карты"); !allCardTitle.isEmpty()) { // 1. Если нет кнопки видимой, свайпаем. Кликаем на "Все продукты" for (int i = 0; i < 5; ++i) { Node allProductsBtn = xmlScreenParser.findButtonNode("Все продукты", false); // Node allProductsBtn = xmlScreenParser.findTextNode("Курсы валют и металлов"); для теста if (allProductsBtn.x() > 0 && allProductsBtn.y() > 0) { qDebug() << "--- All Product in focus"; AdbUtils::makeTap(deviceId, allProductsBtn.x(), allProductsBtn.y()); QThread::msleep(1000); break; } AdbUtils::makeSwipe(deviceId, allCardTitle.x(), allCardTitle.y(), allCardTitle.x(), allCardTitle.y() - 200); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter)); } for (int i = 0; i < 5; ++i) { Node allProductsTitle = xmlScreenParser.findTextNode("Все продукты"); // Проверяем что вверху заголовок if (!allProductsTitle.isEmpty() && allProductsTitle.y() < 300) { qDebug() << "--- All Product screen"; // TODO тут делаем синхронизацию данных все карт Node cardNumberBtn = xmlScreenParser.findButtonNode(cardNumber, true); AdbUtils::makeTap(deviceId, cardNumberBtn.x(), cardNumberBtn.y()); break; } QThread::msleep(1000); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter)); } xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter)); Node filterBtn = xmlScreenParser.findButtonNode("РСХБ Онлайн", false); if (!filterBtn.isEmpty()) { AdbUtils::makeTap(deviceId, filterBtn.x(), filterBtn.y()); QThread::msleep(1000); } else { qWarning() << "--- filterBtn not found"; } xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter)); Node allTransactionFilerBtn = xmlScreenParser.findButtonNode("Все операции", false); if (!allTransactionFilerBtn.isEmpty()) { AdbUtils::makeTap(deviceId, allTransactionFilerBtn.x(), allTransactionFilerBtn.y()); QThread::msleep(1000); } else { qWarning() << "--- allTransactionFilerBtn not found"; } xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter)); for (int i = 0; i < 5; ++i) { QList transactions = xmlScreenParser.getLast2DaysTransactions(); if (!transactions.empty()) { for (const Node &transaction: transactions) { qDebug() << transaction.text; // TODO вот мы дошли до списка транзакций } break; } QThread::msleep(1000); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter)); } // } else { // пишем в БД, что не дошли до домашней страницы, и завершаемся // сохраняем скрин приложения // дделаем алерт } } else { // пишем в БД, что не дошли до домашней страницы, и завершаемся // сохраняем скрин приложения // дделаем алерт } return; // 1. Идем на домашнюю страницу if (goToHomeScreen(deviceId, packageName, pinCode, width, height)) { // мы на главном // перед тем как юзать снова проверить на банеры // Проверяем на банеры после пин-кодом // Это уже для шага дальше // если нулевые координаты, свайпаем вверх, чтобы текст "Счета и карты" был у верхнего края } else { // пишем в БД, что не дошли до домашней страницы, и завершаемся // сохраняем скрин приложения // дделаем алерт } // 2. Идем дальше emit finished(); } void HomeScreenScript::stop() { m_running = false; }