// // Created by AmirS on 24.04.2025. // #include "Script.h" #include "AdbUtils.h" #include "XmlParser.h" #include "QDebug" #include #include #include #include // Вспомогательные функции void wait(const int milliseconds) { std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); } Script::Script(QObject *parent) : QObject(parent) { } Script::~Script() = default; void Script::start() { qDebug("Запуск скрипта..."); processUiLoop(); } void Script::processUiLoop() { while (true) { QString uiDumpPath = "ui_dump.xml"; AdbUtils::dumpAndPullUiXml(uiDumpPath); QList elements = XmlParser::parseUiDumpUsingQXml(uiDumpPath); switch (currentState) { case State::WaitingForPinInput: if (XmlParser::isPinCodeScreen(elements)) { qDebug() << "Экран ввода ПИН-кода обнаружен"; inputPinCode("1211"); currentState = State::ClosingBanner; } break; case State::ClosingBanner: handleClosingBanner(elements); break; case State::OnMainScreen: if (XmlParser::isMainScreen(elements)) { qDebug() << "State: OnMainScreen -> Поиск кнопки 'Все продукты'"; swipeToFindElement("Все продукты"); currentState = State::SearchingCard; } break; case State::SearchingCard: qDebug() << "State: SearchingCard -> Поиск блока карты"; // Добавить сюда поиск карты по последним 4 цифрам, известным для выбора карты currentState = State::OnCardDetails; break; case State::OnCardDetails: // Обработка экрана перевода handleTransferScreen(elements); break; case State::OnTransferScreen: qDebug() << "State: OnTransferScreen -> Перевод выполнен успешно."; currentState = State::Idle; break; case State::Idle: default: qDebug() << "State: Idle -> Waiting for action"; if (XmlParser::isPinCodeScreen(elements)) { currentState = State::WaitingForPinInput; } break; } QThread::sleep(1); // Пауза между проверками экрана } } // Функция для запуска скрипта с передачей параметров // void Script::runScript(const QString& pin, const QString& last4digits) { // qDebug("Запуск скрипта для ru.rshb.dbo"); // // if (!AdbUtils::checkDevices()) { // qDebug("ADB: Нет устройств"); // return; // } // // if (!AdbUtils::startApp("ru.rshb.dbo")) { // qDebug("Не удалось запустить приложение"); // return; // } // // wait(4500); // AdbUtils::dumpAndPullUiXml("ui_01.xml"); // // auto ui = XmlParser::parseUiDumpUsingQXml("ui_01.xml"); // // if (XmlParser::isPinCodeScreen(ui)) { // qDebug("Экран PIN-кода"); // // // Находим координаты цифр на экране и вводим ПИН // std::vector pinButtons = getPinButtons(ui); // for (size_t i = 0; i < pin.length(); ++i) { // char digit = pin[i]; // for (const auto& el : pinButtons) { // if (el.text == std::string(1, digit)) { // AdbUtils::tap((el.x1 + el.x2) / 2, (el.y1 + el.y2) / 2); // break; // } // } // } // wait(1000); // } // // // Появляющиеся после входа по ПИН баннеры — цикл для обработки разных состояний показов модальных окон // bool postLoginScreenFound = false; // int attempts = 5; // Максимальное количество попыток парсинга xml // while (attempts-- > 0) { // AdbUtils::dumpAndPullUiXml("ui_02.xml"); // auto ui2 = XmlParser::parseUiDumpUsingQXml("ui_02.xml"); // // // Закрытие баннеров или диалогов // if (handlePostLoginScreen(ui2)) { // postLoginScreenFound = true; // break; // } // // // Проверка, если мы на главном экране // if (isMainScreen(ui2)) { // qDebug("Главный экран подтверждён"); // postLoginScreenFound = true; // break; // } // // // Ждем немного перед следующим запросом дампа экрана // wait(1000); // } // // if (!postLoginScreenFound) { // qDebug("Не удалось определить пост-логин баннер."); // return; // } // // // Пролистывание до кнопки "Все продукты" // AdbUtils::swipe(1000, 2000, 1000, 1300); // wait(500); // // // Нажимаем на кнопку "Все продукты" // AdbUtils::dumpAndPullUiXml("ui_03.xml"); // auto ui3 = XmlParser::parseUiDumpUsingQXml("ui_03.xml"); // // // Ищем кнопку "Все продукты" и нажимаем на неё // tapByText(ui3, "Все продукты"); // wait(800); // // // Шаг 6: Выбор карты на экране "Все продукты" // // Запрашиваем дамп UI для поиска карт // AdbUtils::dumpAndPullUiXml("ui_04.xml"); // auto ui4 = XmlParser::parseUiDumpUsingQXml("ui_04.xml"); // // // Определим, какую карту выбрать, например, с 4 цифрами "0823" // UiElement cardButton = findCardByLast4Digits(ui4, last4digits); // if (cardButton.text.isEmpty()) { // qDebug("Не найдена карта с номером, содержащим " + last4digits); // return; // } // // // Нажимаем на карту // AdbUtils::tap((cardButton.x1 + cardButton.x2) / 2, (cardButton.y1 + cardButton.y2) / 2); // wait(1000); // // // Нажимаем на кнопку "Оплатить" // // Запрашиваем дамп UI для поиска кнопки "Оплатить" // AdbUtils::dumpAndPullUiXml("ui_05.xml"); // auto ui5 = XmlParser::parseUiDumpUsingQXml("ui_05.xml"); // // // Ищем кнопку "Оплатить" и нажимаем на неё // UiElement payBtn = findPayButton(ui5); // if (payBtn.text.isEmpty()) { // qDebug("Не найдена кнопка 'Оплатить'"); // return; // } // // // Нажимаем на кнопку "Оплатить" // AdbUtils::tap((payBtn.x1 + payBtn.x2) / 2, (payBtn.y1 + payBtn.y2) / 2); // qDebug("Нажата кнопка 'Оплатить'"); // wait(1000); // } // Функции для определения экранов и действий void Script::inputPinCode(const QString &pinCode) { // Перебираем каждый символ из ПИН-кода for (int i = 0; i < pinCode.length(); ++i) { QString digit = pinCode.mid(i, 1); qDebug() << "Finding button for digit: " << digit; // Поиск кнопки с соответствующим текстом QList elements = XmlParser::parseUiDumpUsingQXml("ui_dump.xml"); // Ищем кнопку с цифрой bool found = false; for (const UiElement &el: elements) { if (el.text() == digit && el.isClickable()) { int x = (el.x1() + el.x2()) / 2; int y = (el.y1() + el.y2()) / 2; AdbUtils::tap(x, y); // Тапаем по кнопке found = true; break; } } // Если не нашли кнопку для цифры, выводим предупреждение if (!found) { qWarning() << "Digit button for " << digit << " not found!"; } // Пауза между нажатиями, чтобы избежать слишком быстрого ввода QThread::sleep(1); } } void Script::handleClosingBanner(QList &elements) { while (true) { // Проверка на наличие баннера или диалога if (XmlParser::isBannerOrDialog(elements)) { qDebug() << "State: ClosingBanner -> Found banner/dialog. Closing it."; // Найдем кнопку "Закрыть" или кнопку без текста в правом верхнем углу bool closed = false; for (const UiElement &el: elements) { if (el.isClickable()) { // Флаг для отслеживания, если мы нашли хотя бы одно окно для закрытия bool bannerClosed = false; // Пробуем закрыть баннер, если есть кнопка "Закрыть" if ((el.text() == "Закрыть" || el.text() == "Позже") && el.className() == "android.widget.Button") { int x = (el.x1() + el.x2()) / 2; int y = (el.y1() + el.y2()) / 2; AdbUtils::tap(x, y); // Тапаем по кнопке "Закрыть" closed = true; bannerClosed = true; qDebug() << "Banner/dialog closed!"; break; } // Если кнопка без текста и в правой верхней части if (el.text().isEmpty() && el.className() == "android.widget.Button" && el.x1() > 900 && el.y1() < 100) { int x = (el.x1() + el.x2()) / 2; int y = (el.y1() + el.y2()) / 2; AdbUtils::tap(x, y); // Тапаем по кнопке в правом верхнем углу closed = true; bannerClosed = true; qDebug() << "Banner/dialog closed by tapping button in the top right corner."; break; } } } if (!closed) { qWarning() << "Невозможно закрыть баннер или диалог!"; break; // Прерываем цикл, если не удалось закрыть окно } // Пауза перед следующим закрытием окна QThread::sleep(1); } else { qDebug() << "State: ClosingBanner -> No banners/dialogs left to close."; break; // Заканчиваем цикл, если баннеров/диалогов больше нет } } // Переход к следующему состоянию currentState = State::OnMainScreen; // Если все модальные окна закрыты, переходим к основному экрану } void Script::inputAmount(const QString &amount) { // Разделяем сумму на отдельные цифры for (int i = 0; i < amount.length(); ++i) { QString digit = amount.mid(i, 1); qDebug() << "Inputting digit: " << digit; // Поиск кнопки с соответствующей цифрой QList elements = XmlParser::parseUiDumpUsingQXml("ui_dump.xml"); bool found = false; for (const UiElement &el: elements) { if (el.text() == digit && el.isClickable()) { int x = (el.x1() + el.x2()) / 2; int y = (el.y1() + el.y2()) / 2; AdbUtils::tap(x, y); // Тапаем по кнопке цифры found = true; break; } } if (!found) { qWarning() << "Digit button for " << digit << " not found!"; } QThread::sleep(1); // Пауза между нажатиями } // Если нужно, нажимаем кнопку для завершения ввода суммы QList elements = XmlParser::parseUiDumpUsingQXml("ui_dump.xml"); for (const UiElement &el: elements) { if (el.text() == "ОК" && el.isClickable()) { int x = (el.x1() + el.x2()) / 2; int y = (el.y1() + el.y2()) / 2; AdbUtils::tap(x, y); // Тапаем по кнопке "ОК" break; } } } void Script::swipeToFindElement(const QString &searchText) { bool found = false; int attempts = 0; // Прокручиваем экран несколько раз, пока не найдем элемент или не превысим попытки while (attempts < 5 && !found) { QList elements = XmlParser::parseUiDumpUsingQXml("ui_dump.xml"); // Ищем нужный элемент на экране for (const UiElement &el: elements) { if (el.text() == searchText && el.isClickable()) { qDebug() << "Element found: " << searchText; int x = (el.x1() + el.x2()) / 2; int y = (el.y1() + el.y2()) / 2; AdbUtils::tap(x, y); // Тапаем по найденному элементу found = true; break; } } // Если элемент не найден, выполняем свайп if (!found) { qDebug() << "Element not found, swiping..."; AdbUtils::swipe(500, 1500, 500, 500); // Свайп вверх attempts++; QThread::sleep(2); // Пауза между свайпами } } if (!found) { qWarning() << "Element " << searchText << " not found after " << attempts << " swipe attempts."; } } void Script::handleTransferScreen(QList &elements) { // Проверка на экран перевода if (XmlParser::isTransferScreen(elements)) { qDebug() << "State: OnTransferScreen -> Looking for 'Enter amount' field or 'Confirm' button."; bool found = false; // Если поле для ввода суммы найдено, вводим сумму for (const UiElement &el: elements) { if (el.text() == "Сумма" && el.isClickable()) { qDebug() << "Found 'Amount' input field"; inputAmount("1000"); // Пример суммы found = true; break; } } if (!found) { qWarning() << "'Amount' input field not found!"; } // После ввода суммы ищем кнопку 'Оплатить' или 'Подтвердить' for (const UiElement &el: elements) { if (el.text() == "Оплатить" && el.isClickable()) { int x = (el.x1() + el.x2()) / 2; int y = (el.y1() + el.y2()) / 2; AdbUtils::tap(x, y); // Тапаем по кнопке "Оплатить" qDebug() << "Payment confirmed."; break; } } } } void tapByText(const std::vector& ui, const std::string& text) { for (const auto& el : ui) { if (el.text == QString::fromStdString(text)) { tap((el.x1 + el.x2) / 2, (el.y1 + el.y2) / 2); break; } } } // Тап по полю ввода void tapInputField(const std::vector& ui, const std::string& fieldText) { for (const auto& el : ui) { if (el.text == QString::fromStdString(fieldText)) { tap((el.x1 + el.x2) / 2, (el.y1 + el.y2) / 2); break; } } } // Тап по кнопке "Далее" void tapNextButton(const std::vector& ui) { for (const auto& el : ui) { if (el.text == "Войти" || el.text == "Далее" || el.text == "Продолжить") { tap((el.x1 + el.x2) / 2, (el.y1 + el.y2) / 2); break; } } } bool handlePostLoginScreen(const std::vector& ui) { for (const auto& el : ui) { // Ищем баннеры и модальные окна, которые нужно закрыть if (el.text == "Получайте новости с помощью" || el.text == "Не пропускайте специальные предложения и рекомендации") { // Тапаем по пустой области tap((el.x1 + el.x2) / 2, (el.y1 + el.y2) / 2); wait(900); return true; } if (el.text == "Позже" || el.text == "Окей" || el.text == "Закрыть") { // Тапаем по кнопке закрытия или кнопке "Позже" tap((el.x1 + el.x2) / 2, (el.y1 + el.y2) / 2); wait(900); return true; } } return false; } bool isMainScreen(const std::vector& ui) { for (const auto& el : ui) { if (el.text == "Счета и карты" || el.text == "Доступно баллов") { return true; } } return false; } UiElement findCardByLast4Digits(const std::vector& ui, const std::string& last4digits) { // Проходим по всем элементам UI, которые парсились из XML for (const auto& el : ui) { // Используем регулярное выражение для поиска 4 цифр в тексте std::smatch m; std::string str = el.text.toUtf8().constData(); // Преобразуем QString в std::string // Если в тексте найдено совпадение с регулярным выражением для 4 цифр if (std::regex_search(str, m, std::regex(R"(\*\*\s?(\d{4}))"))) { // Проверяем, совпадают ли последние 4 цифры с переданными if (m[1] == last4digits) { // Если нашли совпадение, возвращаем элемент return el; } } } // Если карта с такими последними цифрами не найдена, возвращаем пустой элемент return {}; } UiElement findPayButton(const std::vector& ui) { for (const auto& el : ui) { if (el.text == "Оплатить") { return el; } } return {}; } void Script::stop() { m_running = false; }