1
0
forked from BRT/arc
arc/automation_script/Script.cpp
2025-04-27 18:35:22 +04:00

505 lines
20 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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;
}