// // Created by AmirS on 24.04.2025. // #include "Script.h" #include "AdbUtils.h" #include "XmlParser.h" #include "QDebug" #include Script::Script(QObject *parent) : QObject(parent) { } Script::~Script() = default; void Script::start() { qDebug() << "Worker started in thread:" << QThread::currentThread(); processUiLoop(); } [[noreturn]] void Script::processUiLoop() { qDebug() << QString::fromUtf8("Запуск скрипта для ru.rshb.dbo"); if (!AdbUtils::checkDevices()) { qDebug() << QString::fromUtf8("ADB: Нет устройств"); return; } if (!AdbUtils::startApp("ru.rshb.dbo")) { qDebug("Не удалось запустить приложение"); return; } QThread::msleep(4500); while (true) { QString uiDumpPath = "ui_dump.xml"; AdbUtils::dumpAndPullUiXml(uiDumpPath); QList elements = XmlParser::parseUiDumpUsingQXml(uiDumpPath); switch (currentState) { case State::HandlingSecurityBanner: handleSecurityBanner(elements); // Переход в состояние ожидания ПИН-кода после закрытия баннера currentState = State::WaitingForPinInput; 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; currentState = State::Idle; break; case State::OnCardDetails: // Обработка экрана деталей банковской карты handleCardDetailsScreen(elements); break; case State::OnTransferScreen: qDebug() << "State: OnTransferScreen -> Перевод выполнен успешно."; // handleTransferScreen(elements); currentState = State::Idle; break; case State::Idle: default: qDebug() << "State: Idle -> Жду следующего действия"; if (XmlParser::isSecurityBanner(elements)) { currentState = State::HandlingSecurityBanner; } else if (XmlParser::isPinCodeScreen(elements)) { currentState = State::WaitingForPinInput; } break; } QThread::msleep(1000); // Пауза между проверками экрана } } // Функция для запуска скрипта с передачей параметров // 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 tapElement(const UiElement &el) { const int x = (el.x1() + el.x2()) / 2; const int y = (el.y1() + el.y2()) / 2; // Тапаем по кнопке AdbUtils::tap(x, y); } // Функции для определения экранов и действий void Script::handleSecurityBanner(const QList& elements) { if (XmlParser::isSecurityBanner(elements)) { qDebug() << "State: HandlingSecurityBanner -> Найден баннер о безопасности."; // Ищем кликабельную кнопку "Отложить" for (const UiElement& el : elements) { if (el.text() == "Отложить" && el.isClickable() && el.className() == "android.widget.Button") { qDebug() << "Найдена кнопка 'Отложить'. Нажимаем её!"; tapElement(el); // Нажимаем на кнопку "Отложить" break; } } } } void Script::inputPinCode(const QString &pinCode) { // Перебираем каждый символ из ПИН-кода for (int i = 0; i < pinCode.length(); ++i) { QString digit = pinCode.mid(i, 1); qDebug() << "Найдена кнопка для символа: " << digit; // Поиск кнопки с соответствующим текстом QList elements = XmlParser::parseUiDumpUsingQXml("ui_dump.xml"); // Ищем кнопку с цифрой bool found = false; for (const UiElement &el: elements) { if (el.text() == digit && el.isClickable()) { tapElement(el); found = true; break; } } // Если не нашли кнопку для цифры, выводим предупреждение if (!found) { qWarning() << "Цифровая кнопка для " << digit << " не найдена!"; } // Пауза между нажатиями, чтобы избежать слишком быстрого ввода QThread::msleep(200); } } void Script::handleClosingBanner(const QList &elements) { while (true) { // Проверка на наличие баннера или диалога if (XmlParser::isBannerOrDialog(elements)) { qDebug() << "State: ClosingBanner -> Найден баннер/диалог. Закрываем его."; // Флаг для отслеживания, если мы нашли хотя бы одно окно для закрытия bool closed = false; for (const UiElement &el: elements) { if (el.isClickable()) { // Пробуем закрыть баннер, если есть кнопка "Закрыть" if ((el.text() == "Закрыть" || el.text() == "Позже") && el.className() == "android.widget.Button") { tapElement(el); closed = true; qDebug() << "Баннер/диалог закрыт!"; break; } // Если кнопка без текста и в правой верхней части (проверяем по координатам приблизительно) if (el.text().isEmpty() && el.className() == "android.widget.Button" && el.x1() > 800 && el.y1() < 200) { tapElement(el); closed = true; qDebug() << "Баннер/диалог закрыт по нажатию кнопки в правом верхнем углу."; break; } } } if (!closed) { qWarning() << "Невозможно закрыть баннер или диалог!"; break; // Прерываем цикл, если не удалось закрыть окно } // Пауза перед следующим закрытием окна QThread::msleep(1000); } else { qDebug() << "State: ClosingBanner -> Не осталось баннеров/диалогов для закрытия."; // Заканчиваем цикл, если баннеров/диалогов больше нет break; } } // Переход к следующему состоянию // Если все модальные окна закрыты, переходим к основному экрану currentState = State::OnMainScreen; } 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() && el.className() == "android.widget.Button") { qDebug() << "Найден элемент: " << searchText; tapElement(el); found = true; break; } } // Если элемент не найден, выполняем свайп, прибавляем попытку и ищем снова if (!found) { qDebug() << "Элемент не найден, свайпаем..."; // Свайп вверх AdbUtils::swipe(500, 1500, 500, 500); attempts++; // Пауза между свайпами QThread::msleep(1000); } } if (!found) { qWarning() << "Элемент " << searchText << " не найден после " << attempts << " попыток свайпа."; } } void Script::handleCardDetailsScreen(const QList& elements) { // Проверяем, что мы на экране детали карты if (XmlParser::isDetailCardScreen(elements)) { qDebug() << "State: OnCardDetails -> Найден экран детализированной карты."; // Ищем кликабельную кнопку "Оплатить" bool foundPaymentButton = false; for (const UiElement& el : elements) { if (el.text() == "Оплатить" && el.isClickable()) { qDebug() << "Найдена кнопка 'Оплатить'. Нажимаем её!"; // Нажимаем на кнопку "Оплатить" tapElement(el); foundPaymentButton = true; break; } } if (!foundPaymentButton) { qWarning() << "Payment button 'Оплатить' not found on the card details screen."; } } } // void Script::handleTransferScreen(const 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) { // qDebug() << "Поле ввода 'Сумма' не найдено!"; // } // // // После ввода суммы ищем кнопку 'Оплатить' или 'Подтвердить' // for (const UiElement &el: elements) { // if ((el.text() == "Оплатить" || el.text() == "Подтвердить") && el.isClickable()) { // tapElement(el); // qDebug() << "Payment confirmed."; // break; // } // } // } // } void Script::inputAmount(const QString &amount) { // Разделяем сумму на отдельные цифры for (int i = 0; i < amount.length(); ++i) { QString digit = amount.mid(i, 1); qDebug() << "Вводим цифру: " << digit; // Поиск кнопки с соответствующей цифрой QList elements = XmlParser::parseUiDumpUsingQXml("ui_dump.xml"); bool found = false; for (const UiElement &el: elements) { if (el.text() == digit && el.isClickable()) { tapElement(el); found = true; break; } } if (!found) { qWarning() << "Цифровая кнопка для " << digit << " не найдена!"; } QThread::msleep(1000); // Пауза между нажатиями } // Если нужно, нажимаем кнопку для завершения ввода суммы QList elements = XmlParser::parseUiDumpUsingQXml("ui_dump.xml"); for (const UiElement &el: elements) { if (el.isClickable()) { if (el.text() == "ОК" || el.text() == "Подтвердить" || el.text() == "Готово") { tapElement(el); 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; // } // } // } // // 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; }