From df0f9e77815f4b5cc14e2471b11e297aa3bb466d Mon Sep 17 00:00:00 2001 From: AmirS Date: Fri, 25 Apr 2025 15:42:51 +0400 Subject: [PATCH] Script auto starting --- CMakeLists.txt | 6 + automation_script/adb_fun.cpp | 61 +++++++ automation_script/adb_fun.h | 18 +++ automation_script/script_worker.cpp | 240 ++++++++++++++++++++++++++++ automation_script/script_worker.h | 49 ++++++ automation_script/xml_reader.cpp | 79 +++++++++ automation_script/xml_reader.h | 15 ++ main.cpp | 107 +------------ 8 files changed, 470 insertions(+), 105 deletions(-) create mode 100644 automation_script/adb_fun.cpp create mode 100644 automation_script/adb_fun.h create mode 100644 automation_script/script_worker.cpp create mode 100644 automation_script/script_worker.h create mode 100644 automation_script/xml_reader.cpp create mode 100644 automation_script/xml_reader.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ca49686..c4f10bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,12 @@ add_executable(ARCDesktopProject ${SERVICES_SOURCES} ${DB_SOURCES} ${MODELS_SOURCES} + automation_script/adb_fun.cpp + automation_script/adb_fun.h + automation_script/xml_reader.cpp + automation_script/xml_reader.h + automation_script/script_worker.cpp + automation_script/script_worker.h ) if (WIN32) diff --git a/automation_script/adb_fun.cpp b/automation_script/adb_fun.cpp new file mode 100644 index 0000000..e56ec12 --- /dev/null +++ b/automation_script/adb_fun.cpp @@ -0,0 +1,61 @@ +#include "adb_fun.h" +#include +#include +#include +#include +#include +#include +#include + +std::string exec(const std::string& cmd) { + std::array buffer{}; + std::string result; + FILE* pipe = _popen(cmd.c_str(), "r"); + if (!pipe) return "ERROR"; + while (fgets(buffer.data(), buffer.size(), pipe)) { + result += buffer.data(); + } + _pclose(pipe); + return result; +} + +bool checkDevices() { + std::string out = exec("adb devices"); + return out.find("\tdevice") != std::string::npos; +} + +bool startApp(const std::string& packageName) { + std::string cmd = "adb shell monkey -p " + packageName + " -c android.intent.category.LAUNCHER 1"; + return system(cmd.c_str()) == 0; +} + +bool tap(int x, int y) { + std::stringstream ss; + ss << "adb shell input tap " << x << " " << y; + return system(ss.str().c_str()) == 0; +} + +bool swipe(const int x1, const int y1, const int x2, const int y2) { + std::stringstream ss; + ss << "adb shell input swipe " << x1 << " " << y1 << " " << x2 << " " << y2; + return system(ss.str().c_str()) == 0; +} + +bool inputText(const std::string& text) { + std::stringstream ss; + ss << "adb shell input text \"" << text << "\""; + return system(ss.str().c_str()) == 0; +} + +bool takeScreenshot(const std::string& filename) { + system("mkdir screens 2>nul"); // Windows + std::string cmd = "adb exec-out screencap -p > screens/" + filename; + return system(cmd.c_str()) == 0; +} + +void log(const std::string& message) { + std::ofstream out("log.txt", std::ios::app); + std::time_t t = std::time(nullptr); + out << "[" << std::put_time(std::localtime(&t), "%H:%M:%S") << "] " << message << std::endl; + std::cout << message << std::endl; +} \ No newline at end of file diff --git a/automation_script/adb_fun.h b/automation_script/adb_fun.h new file mode 100644 index 0000000..9200165 --- /dev/null +++ b/automation_script/adb_fun.h @@ -0,0 +1,18 @@ +#ifndef ADB_FUN_H +#define ADB_FUN_H + +#include +#include + +bool checkDevices(); +bool startApp(const std::string& packageName); +bool tap(int x, int y); +bool inputText(const std::string& text); +bool swipe(int x1, int y1, int x2, int y2); +bool takeScreenshot(const std::string& filename); + +void log(const std::string& message); + + + +#endif //ADB_FUN_H diff --git a/automation_script/script_worker.cpp b/automation_script/script_worker.cpp new file mode 100644 index 0000000..2043666 --- /dev/null +++ b/automation_script/script_worker.cpp @@ -0,0 +1,240 @@ +// +// Created by Misterio on 24.04.2025. +// + +#include "script_worker.h" +#include "adb_fun.h" +#include "xml_reader.h" +#include +#include +#include +#include + +// Вспомогательные функции + +void wait(const int milliseconds) { + std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); +} + +// Функция для запуска скрипта с передачей параметров +void runScriptWorker(const std::string& pin, const std::string& last4digits) { + log("Запуск скрипта для ru.rshb.dbo"); + + if (!checkDevices()) { + log("ADB: Нет устройств"); + qDebug("ADB: Нет устройств"); + return; + } + + if (!startApp("ru.rshb.dbo")) { + log("Не удалось запустить приложение"); + qDebug("Не удалось запустить приложение"); + return; + } + + wait(4500); + dumpAndPullUiXml("ui_01.xml"); + + auto ui = parseUiDumpUsingQXml("ui_01.xml"); + + if (isPinScreen(ui)) { + log("Экран PIN-кода"); + 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)) { + tap((el.x1 + el.x2) / 2, (el.y1 + el.y2) / 2); + break; + } + } + } + wait(1000); + } + + // Пост-логин баннеры — цикл для обработки разных состояний показов модальных окон + bool postLoginScreenFound = false; + int attempts = 5; // Максимальное количество попыток парсинга xml + while (attempts-- > 0) { + dumpAndPullUiXml("ui_02.xml"); + auto ui2 = parseUiDumpUsingQXml("ui_02.xml"); + + // Закрытие баннеров или диалогов + if (handlePostLoginScreen(ui2)) { + postLoginScreenFound = true; + break; + } + + // Проверка, если мы на главном экране + if (isMainScreen(ui2)) { + log("Главный экран подтверждён"); + postLoginScreenFound = true; + break; + } + + // Ждем немного перед следующим запросом дампа экрана + wait(1000); + } + + if (!postLoginScreenFound) { + log("Не удалось определить пост-логин баннер."); + return; + } + + // Пролистывание до кнопки "Все продукты" + swipe(1000, 2000, 1000, 1300); + wait(500); + + // Нажимаем на кнопку "Все продукты" + dumpAndPullUiXml("ui_03.xml"); + auto ui3 = parseUiDumpUsingQXml("ui_03.xml"); + + // Ищем кнопку "Все продукты" и нажимаем на неё + tapByText(ui3, "Все продукты"); + wait(800); + + // Шаг 6: Выбор карты на экране "Все продукты" + // Запрашиваем дамп UI для поиска карт + dumpAndPullUiXml("ui_04.xml"); + auto ui4 = parseUiDumpUsingQXml("ui_04.xml"); + + // Определим, какую карту выбрать, например, с 4 цифрами "0823" + UiElement cardButton = findCardByLast4Digits(ui4, last4digits); + if (cardButton.text.isEmpty()) { + log("Не найдена карта с номером, содержащим " + last4digits); + return; + } + + // Нажимаем на карту + tap((cardButton.x1 + cardButton.x2) / 2, (cardButton.y1 + cardButton.y2) / 2); + wait(1000); + + // Нажимаем на кнопку "Оплатить" + // Запрашиваем дамп UI для поиска кнопки "Оплатить" + dumpAndPullUiXml("ui_05.xml"); + auto ui5 = parseUiDumpUsingQXml("ui_05.xml"); + + // Ищем кнопку "Оплатить" и нажимаем на неё + UiElement payBtn = findPayButton(ui5); + if (payBtn.text.isEmpty()) { + log("Не найдена кнопка 'Оплатить'"); + return; + } + + // Нажимаем на кнопку "Оплатить" + tap((payBtn.x1 + payBtn.x2) / 2, (payBtn.y1 + payBtn.y2) / 2); + log("Нажата кнопка 'Оплатить'"); + wait(1000); +} + +// Функции для определения экранов и действий + +bool isPinScreen(const std::vector& ui) { + for (const auto& el : ui) { + if (el.text == "Введите ПИН") { + return true; + } + } + return false; +} + +std::vector getPinButtons(const std::vector& ui) { + std::vector pinButtons; + for (const auto& el : ui) { + if (el.text.length() == 1 && std::isdigit(el.text[0].toLatin1())) { + pinButtons.push_back(el); // Собираем все элементы с цифрами + } + } + return pinButtons; +} + +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 {}; +} + diff --git a/automation_script/script_worker.h b/automation_script/script_worker.h new file mode 100644 index 0000000..3817fd7 --- /dev/null +++ b/automation_script/script_worker.h @@ -0,0 +1,49 @@ +// +// Created by Misterio on 24.04.2025. +// + +#ifndef SCRIPT_WORKER_H +#define SCRIPT_WORKER_H +#include +#include +#include "xml_reader.h" + +// Функция для запуска скрипта с передачей параметров +void runScriptWorker(const std::string& pin, const std::string& last4digits); + +// Функции для определения различных экранов и действий + +// Проверка, что перед нами экран логина +bool isLoginScreen(const std::vector& ui); + +// Проверка, что перед нами экран ввода ПИН +bool isPinScreen(const std::vector& ui); + +// Получение списка кнопок с цифрами для ввода ПИН +std::vector getPinButtons(const std::vector& ui); + +// Тап по элементу с текстом +void tapByText(const std::vector& ui, const std::string& text); + +// Тап по полю ввода +void tapInputField(const std::vector& ui, const std::string& fieldText); + +// Тап по кнопке "Далее" и её вариациям +void tapNextButton(const std::vector& ui); + +// Обработка экрана с пост-логин баннерами или диалогами +bool handlePostLoginScreen(const std::vector& ui); + +// Проверка, что перед нами главный экран +bool isMainScreen(const std::vector& ui); + +// Тап по главному экрану +void tapMainScreen(const std::vector& ui); + +// Функция для поиска карты по последним 4 цифрам +UiElement findCardByLast4Digits(const std::vector& ui, const std::string& last4digits); + +// Поиск кнопки "Оплатить" на экране +UiElement findPayButton(const std::vector& ui); + +#endif //SCRIPT_WORKER_H diff --git a/automation_script/xml_reader.cpp b/automation_script/xml_reader.cpp new file mode 100644 index 0000000..0f85dfa --- /dev/null +++ b/automation_script/xml_reader.cpp @@ -0,0 +1,79 @@ +#include "xml_reader.h" + +#include +#include +#include +#include +#include + + +bool dumpAndPullUiXml(const std::string& localPath) { + int r1 = system("adb shell uiautomator dump /sdcard/view.xml"); + int r2 = system(("adb pull /sdcard/view.xml " + localPath).c_str()); + return r1 == 0 && r2 == 0; +} + +std::vector parseUiDumpUsingQXml(const QString& filePath) { + std::vector uiElements; + + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qWarning() << "Не удалось открыть файл:" << filePath; + return uiElements; + } + + QXmlStreamReader xmlReader(&file); + UiElement currentElement; + + while (!xmlReader.atEnd()) { + xmlReader.readNext(); + + if (xmlReader.hasError()) { + qDebug() << "XML parsing error at line" << xmlReader.lineNumber() << ":" + << xmlReader.errorString(); + return {}; // Возвращаем пустой вектор в случае ошибки + } + + if (xmlReader.isStartElement()) { + qDebug() << "Found element:" << xmlReader.name(); + if (xmlReader.name() == "node") { + for (const QXmlStreamAttribute &attr : xmlReader.attributes()) { + if (attr.name() == "bounds") { + // Извлекаем координаты из bounds + QString bounds = attr.value().toString(); + QRegularExpression regex(R"(\[([0-9]+),([0-9]+)\]\[([0-9]+),([0-9]+)\])"); + QRegularExpressionMatch match = regex.match(bounds); + + if (match.hasMatch()) { + // Извлекаем координаты из регулярного выражения + currentElement.x1 = match.captured(1).toInt(); + currentElement.y1 = match.captured(2).toInt(); + currentElement.x2 = match.captured(3).toInt(); + currentElement.y2 = match.captured(4).toInt(); + } + } + if (attr.name() == "text") { + currentElement.text = attr.value().toString(); + } + if (attr.name() == "resource-id") { + currentElement.resourceId = attr.value().toString(); + } + } + uiElements.push_back(currentElement); + } + } + + // Пропускаем закрывающий тег + if (xmlReader.isEndElement()) { + qDebug() << "End element:" << xmlReader.name(); + } + } + + if (xmlReader.hasError()) { + qWarning() << "Final XML parsing error:" << xmlReader.errorString(); + } + + return uiElements; +} + + diff --git a/automation_script/xml_reader.h b/automation_script/xml_reader.h new file mode 100644 index 0000000..d7038c8 --- /dev/null +++ b/automation_script/xml_reader.h @@ -0,0 +1,15 @@ +#ifndef XML_READER_H +#define XML_READER_H +#include + +struct UiElement { + int x1, y1, x2, y2; + QString text; + QString resourceId; +}; + +std::vector parseUiDumpUsingQXml(const QString &filePath); +bool dumpAndPullUiXml(const std::string& localPath); + + +#endif //XML_READER_H diff --git a/main.cpp b/main.cpp index cddc355..2d1500d 100644 --- a/main.cpp +++ b/main.cpp @@ -8,6 +8,7 @@ #include "database/dao/CommonDataDAO.h" #include "db/AccountInfoScreener.h" #include "services/DeviceScreener.h" +#include "automation_script/script_worker.h" #include #include @@ -44,113 +45,9 @@ void setupWorkerAndThread(QCoreApplication &app) { thread->start(); } -void runADBCommand(const std::string& command) { - // Формируем команду для запуска в shell - std::string fullCommand = "adb shell " + command; - std::cout << "Executing: " << fullCommand << std::endl; - - // Запуск команды - int result = std::system(fullCommand.c_str()); - - // Проверка на успешность выполнения - if (result != 0) { - std::cerr << "Error executing command: " << fullCommand << std::endl; - } -} - -void wait(const int milliseconds) { - std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); -} - int main(int argc, char *argv[]) { - // Шаг 1: запускаем приложение РСХБ - runADBCommand("monkey -p ru.rshb.dbo -c android.intent.category.LAUNCHER 1"); - - wait(4500); // Ждем пока загружается приложение, перед экраном ввода пин-кода - - // Шаг 2: Ввод ПИН - runADBCommand("input tap 275 900"); // Клик по цифре 1 - runADBCommand("input tap 560 900"); // Клик по цифре 2 - runADBCommand("input tap 275 900"); // Клик по цифре 1 - runADBCommand("input tap 275 900"); // Клик по цифре 1 - - wait(5500); - - // Шаг 3: Закрытие рекламы - runADBCommand("input tap 300 2090"); // Закрыть баннер пуш-уведомлений (Баннер высвечивается при каждом 2-м запуске) - // runADBCommand("input tap 993 163"); // Закрыть сторис (только при первом входе после логина) - // runADBCommand("input tap 920 151"); // Закрыть окно перед главным экраном (только при первом входе после логина) - // runADBCommand("input tap 990 170"); // Закрыть другое окно (только при первом входе после логина) - - wait(1500); - - // Шаг 4: Пролистывание до кнопки "Все продукты" - runADBCommand("input swipe 1000 2000 1000 1300"); - - wait(3000); - - // Шаг 5: Нажимаем на кнопку "Все продукты" - runADBCommand("input tap 570 1240"); - - wait(3000); - - // Шаг 6: Выбор карты - // runADBCommand("input tap 530 535"); // Для карты дебетовая, цифровая, **6387 - runADBCommand("input tap 530 675"); // Для карты дебетовая, **0823 - // runADBCommand("input tap 530 800"); // Для карты дебетовая, **7498 - - wait(1500); - - // Шаг 7: Нажимаем на кнопку "Оплатить" - runADBCommand("input tap 430 920"); - - wait(1200); - - // Шаг 8: Нажимаем на блок "По номеру телефона" - runADBCommand("input tap 260 1500"); - - wait(2000); - - // Шаг 8.1: Закрытие окна разрешения доступа к контактам - // runADBCommand("input tap 540 2140"); - // wait(500); - - // Вводим номер телефона для перевода - runADBCommand("input tap 250 250"); - runADBCommand("input text '9272960333'"); - - wait(1200); - - // Шаг 9: Нажимаем на область "Другой банк через СБП" - runADBCommand("input tap 500 600"); - - wait(2000); - - // Шаг 10: Выбор банка - // runADBCommand("input tap 280 280"); // Нажимаем для фокуса у поля ввода текста - // runADBCommand("input text 'Т-Банк'"); // Передаем текст 'Т-банк' (Проблема - выдает NullPointerException из-за кириллицы) - runADBCommand("input tap 420 620"); // Т-Банк (Пока выбираю хардкодовым нажатием по списку) - - wait(2000); - - // Шаг 11: Ввод суммы - runADBCommand("input tap 300 1040"); - runADBCommand("input text '10'"); - - wait(2000); - - // Шаг 11.1: Подтверждаем перевод - runADBCommand("input tap 500 2170"); - - wait(2000); - - // Завершаем перевод - runADBCommand("input tap 500 2170"); - - std::cout << "All commands executed successfully." << std::endl; - - + runScriptWorker("1211", "0823"); // Просто запускаем script return 0; QApplication app(argc, argv);