1
0
forked from BRT/arc
arc/automation_script/Script.cpp
2025-04-27 06:16:54 +04:00

484 lines
19 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>
#include <string>
#include <thread>
#include <chrono>
// Вспомогательные функции
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<UiElement> 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<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 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<UiElement> 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<UiElement> &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<UiElement> 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<UiElement> 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<UiElement> 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<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) {
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<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;
}
}
}
bool handlePostLoginScreen(const std::vector<UiElement>& 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<UiElement>& ui) {
for (const auto& el : ui) {
if (el.text == "Счета и карты" || el.text == "Доступно баллов") {
return true;
}
}
return false;
}
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;
}