505 lines
20 KiB
C++
505 lines
20 KiB
C++
//
|
||
// Created by AmirS on 24.04.2025.
|
||
//
|
||
|
||
#include "Script.h"
|
||
#include "AdbUtils.h"
|
||
#include "XmlParser.h"
|
||
#include "QDebug"
|
||
#include <regex>
|
||
|
||
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<UiElement> 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<UiElement> 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<UiElement>& 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<UiElement> 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<UiElement> &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<UiElement> 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<UiElement>& 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<UiElement> &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<UiElement> 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<UiElement> 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<UiElement> &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<UiElement> &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<UiElement> &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<UiElement> &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<UiElement> &ui) {
|
||
// for (const auto &el: ui) {
|
||
// if (el.text == "Оплатить") {
|
||
// return el;
|
||
// }
|
||
// }
|
||
// return {};
|
||
// }
|
||
|
||
void Script::stop() {
|
||
m_running = false;
|
||
}
|