Auto Payment Script improvements)
This commit is contained in:
parent
9f4ae0d726
commit
2a75604d79
84
automation_script/AdbUtils.cpp
Normal file
84
automation_script/AdbUtils.cpp
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
#include "AdbUtils.h"
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QString>
|
||||||
|
#include <QDir>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
AdbUtils::AdbUtils(QObject *parent) : QObject(parent) {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AdbUtils::checkDevices() {
|
||||||
|
QProcess process;
|
||||||
|
process.start("adb devices");
|
||||||
|
process.waitForFinished();
|
||||||
|
QString output = process.readAllStandardOutput();
|
||||||
|
return output.contains("device");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AdbUtils::startApp(const QString &packageName) {
|
||||||
|
QProcess process;
|
||||||
|
QString command = QString("adb shell monkey -p %1 -c android.intent.category.LAUNCHER 1").arg(packageName);
|
||||||
|
process.start(command);
|
||||||
|
process.waitForFinished();
|
||||||
|
return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AdbUtils::dumpAndPullUiXml(const QString &localPath) {
|
||||||
|
QProcess process;
|
||||||
|
process.start("adb shell uiautomator dump /sdcard/view.xml");
|
||||||
|
process.waitForFinished();
|
||||||
|
if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) {
|
||||||
|
qDebug() << "Ошибка в получении дампа UI на устройстве";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString pullCmd = QString("adb pull /sdcard/view.xml %1").arg(localPath);
|
||||||
|
process.start(pullCmd);
|
||||||
|
process.waitForFinished();
|
||||||
|
if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) {
|
||||||
|
qDebug() << "Ошибка в передаче xml файла на компьютер";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AdbUtils::tap(int x, int y) {
|
||||||
|
QProcess process;
|
||||||
|
QString cmd = QString("adb shell input tap %1 %2").arg(x).arg(y);
|
||||||
|
process.start(cmd);
|
||||||
|
process.waitForFinished();
|
||||||
|
return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AdbUtils::swipe(const int x1, const int y1, const int x2, const int y2) {
|
||||||
|
QProcess process;
|
||||||
|
QString cmd = QString("adb shell input swipe %1 %2 %3 %4").arg(x1).arg(y1).arg(x2).arg(y2);
|
||||||
|
process.start(cmd);
|
||||||
|
process.waitForFinished();
|
||||||
|
return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AdbUtils::inputText(const QString &text) {
|
||||||
|
QProcess process;
|
||||||
|
QString cmd = QString("adb shell input text \"%1\"").arg(text);
|
||||||
|
process.start(cmd);
|
||||||
|
process.waitForFinished();
|
||||||
|
return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bool AdbUtils::takeScreenshot(const QString& filename) {
|
||||||
|
// QProcess process;
|
||||||
|
// QDir().mkdir("screens"); // Создаем папку, если её нет
|
||||||
|
// QString cmd = QString("adb exec-out screencap -p > screens/%1").arg(filename);
|
||||||
|
// process.start(cmd);
|
||||||
|
// process.waitForFinished();
|
||||||
|
// return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// void AdbUtils::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;
|
||||||
|
// }
|
||||||
29
automation_script/AdbUtils.h
Normal file
29
automation_script/AdbUtils.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#ifndef ADBUTILS_H
|
||||||
|
#define ADBUTILS_H
|
||||||
|
|
||||||
|
#include <QProcess>
|
||||||
|
|
||||||
|
class AdbUtils : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AdbUtils(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
static bool checkDevices();
|
||||||
|
|
||||||
|
static bool startApp(const QString &packageName);
|
||||||
|
|
||||||
|
static bool tap(int x, int y);
|
||||||
|
|
||||||
|
static bool inputText(const QString &text);
|
||||||
|
|
||||||
|
static bool swipe(int x1, int y1, int x2, int y2);
|
||||||
|
|
||||||
|
static bool dumpAndPullUiXml(const QString &localPath);
|
||||||
|
|
||||||
|
// static bool takeScreenshot(const QString& filename);
|
||||||
|
|
||||||
|
// void log(const QString& message);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ADBUTILS_H
|
||||||
483
automation_script/Script.cpp
Normal file
483
automation_script/Script.cpp
Normal file
@ -0,0 +1,483 @@
|
|||||||
|
//
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
106
automation_script/Script.h
Normal file
106
automation_script/Script.h
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
//
|
||||||
|
// Created by Misterio on 24.04.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef SCRIPT_H
|
||||||
|
#define SCRIPT_H
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include "AdbUtils.h"
|
||||||
|
#include "XmlParser.h"
|
||||||
|
#include "auto_payment/UiElement.h"
|
||||||
|
#include <QObject>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
|
class Script final : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Script(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
~Script() override;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void start(); // Старт фоновой работы
|
||||||
|
|
||||||
|
void stop(); // Стоп фоновой работы
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void finished(); // Сигнал о завершении работы
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_running = true;
|
||||||
|
|
||||||
|
// Функция для запуска скрипта с передачей параметров
|
||||||
|
void runScript(const QString &pin, const QString &last4digits);
|
||||||
|
|
||||||
|
// Функции для определения различных экранов и действий
|
||||||
|
|
||||||
|
// Проверка, что перед нами экран логина
|
||||||
|
bool isLoginScreen(const QList<UiElement> &ui);
|
||||||
|
|
||||||
|
// Проверка, что перед нами экран ввода ПИН
|
||||||
|
bool isPinScreen(const QList<UiElement> &ui);
|
||||||
|
|
||||||
|
// Получение списка кнопок с цифрами для ввода ПИН
|
||||||
|
std::vector<UiElement> getPinButtons(const QList<UiElement> &ui);
|
||||||
|
|
||||||
|
// Тап по элементу с текстом
|
||||||
|
void tapByText(const QList<UiElement> &ui, const QString &text);
|
||||||
|
|
||||||
|
// Тап по полю ввода
|
||||||
|
void tapInputField(const QList<UiElement> &ui, const QString &fieldText);
|
||||||
|
|
||||||
|
// Тап по кнопке "Далее" и её вариациям
|
||||||
|
void tapNextButton(const QList<UiElement> &ui);
|
||||||
|
|
||||||
|
// Обработка экрана с пост-логин баннерами или диалогами
|
||||||
|
bool handlePostLoginScreen(const QList<UiElement> &ui);
|
||||||
|
|
||||||
|
// Проверка, что перед нами главный экран
|
||||||
|
bool isMainScreen(const QList<UiElement> &ui);
|
||||||
|
|
||||||
|
// Тап по главному экрану
|
||||||
|
void tapMainScreen(const QList<UiElement> &ui);
|
||||||
|
|
||||||
|
// Функция для поиска карты по последним 4 цифрам
|
||||||
|
UiElement findCardByLast4Digits(const QList<UiElement> &ui, const QString &last4digits);
|
||||||
|
|
||||||
|
// Поиск кнопки "Оплатить" на экране
|
||||||
|
UiElement findPayButton(const QList<UiElement> &ui);
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class State {
|
||||||
|
WaitingForPinInput,
|
||||||
|
ClosingBanner,
|
||||||
|
OnMainScreen,
|
||||||
|
SearchingCard,
|
||||||
|
OnCardDetails,
|
||||||
|
OnTransferScreen,
|
||||||
|
Idle
|
||||||
|
};
|
||||||
|
|
||||||
|
// Основной цикл скрипта
|
||||||
|
void processUiLoop();
|
||||||
|
|
||||||
|
void handleState(const QList<UiElement> &elements);
|
||||||
|
|
||||||
|
static void inputPinCode(const QString &pinCode);
|
||||||
|
|
||||||
|
void handleClosingBanner(QList<UiElement> &elements);
|
||||||
|
|
||||||
|
static void swipeToFindElement(const QString &searchText);
|
||||||
|
|
||||||
|
void handleTransferScreen(QList<UiElement> &elements);
|
||||||
|
|
||||||
|
static void inputAmount(const QString &amount);
|
||||||
|
|
||||||
|
// Утилиты для работы с ADB
|
||||||
|
AdbUtils adbUtils;
|
||||||
|
// Парсер XML UI дампов мобильного устройства
|
||||||
|
XmlParser xmlParser;
|
||||||
|
// Текущее состояние скрипта автоматизации
|
||||||
|
State currentState = State::Idle;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //SCRIPT_H
|
||||||
103
automation_script/XmlParser.cpp
Normal file
103
automation_script/XmlParser.cpp
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
//
|
||||||
|
// Created by AmirS on 25.04.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "XmlParser.h"
|
||||||
|
#include <QFile>
|
||||||
|
#include <QXmlStreamReader>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
XmlParser::XmlParser(QObject *parent) : QObject(parent) {
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<UiElement> XmlParser::parseUiDumpUsingQXml(const QString &filepath) {
|
||||||
|
QFile file(filepath);
|
||||||
|
QList<UiElement> elements;
|
||||||
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
|
qDebug() << "Не удалось открыть XML файл:" << filepath;
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
QXmlStreamReader xmlReader(&file);
|
||||||
|
|
||||||
|
while (!xmlReader.atEnd()) {
|
||||||
|
xmlReader.readNext();
|
||||||
|
|
||||||
|
if (xmlReader.hasError()) {
|
||||||
|
qDebug() << "Ошибка парсинга XML в линии" << xmlReader.lineNumber() << ":"
|
||||||
|
<< xmlReader.errorString();
|
||||||
|
qCritical() << "Ошибка парсинга XML: " << xmlReader.errorString()
|
||||||
|
<< ", далее вернем пустой элемент";
|
||||||
|
return {}; // Возвращаем пустоту в случае ошибки
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверка на начало элемента
|
||||||
|
if (xmlReader.isStartElement()) {
|
||||||
|
if (xmlReader.name() == "node") {
|
||||||
|
// Создаем элемент из текущего узла
|
||||||
|
UiElement element = UiElement::fromXmlNode(xmlReader);
|
||||||
|
elements.append(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xmlReader.hasError()) {
|
||||||
|
qWarning() << "Ошибка парсинга XML в конце:" << xmlReader.errorString();
|
||||||
|
qDebug() << "Ошибка парсинга XML в конце:" << xmlReader.errorString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool XmlParser::isPinCodeScreen(const QList<UiElement> &elements) {
|
||||||
|
for (const UiElement &element: elements) {
|
||||||
|
// Проверка на наличие текста для ввода PIN
|
||||||
|
if (element.text().contains("Введите ПИН")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool XmlParser::isBannerOrDialog(const QList<UiElement> &elements) {
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
// 1. Проверка на наличие кнопок "Позже" и "Закрыть"
|
||||||
|
for (const UiElement &element: elements) {
|
||||||
|
// Проверка, что кнопка кликабельна и имеет текст "Позже" или "Закрыть"
|
||||||
|
if (element.isClickable()) {
|
||||||
|
if ((element.text() == "Позже" || element.text() == "Закрыть") &&
|
||||||
|
element.className() == "android.widget.Button") {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Если не нашли "Позже" или "Закрыть", ищем кнопку без текста в правой верхней части экрана
|
||||||
|
if (!found) {
|
||||||
|
for (const UiElement &element: elements) {
|
||||||
|
if (element.isClickable() && element.className() == "android.widget.Button" &&
|
||||||
|
element.text().isEmpty()) {
|
||||||
|
// Проверка, что кнопка в правой верхней части экрана
|
||||||
|
// ~ Координаты в правом верхнем углу
|
||||||
|
if (element.x1() > 800 && element.y1() < 200) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool XmlParser::isMainScreen(const QList<UiElement> &elements) {
|
||||||
|
for (const UiElement &element: elements) {
|
||||||
|
// Проверка на кликабельную кнопку "Все продукты"
|
||||||
|
if (element.text().contains("Все продукты") && element.isClickable()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
24
automation_script/XmlParser.h
Normal file
24
automation_script/XmlParser.h
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#ifndef XMLPARSER_H
|
||||||
|
#define XMLPARSER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QXmlStreamReader>
|
||||||
|
#include <QList>
|
||||||
|
#include "auto_payment/UiElement.h"
|
||||||
|
|
||||||
|
class XmlParser final : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit XmlParser(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
static QList<UiElement> parseUiDumpUsingQXml(const QString &filepath);
|
||||||
|
static bool isPinCodeScreen(const QList<UiElement>& elements);
|
||||||
|
static bool isBannerOrDialog(const QList<UiElement>& elements);
|
||||||
|
static bool isMainScreen(const QList<UiElement>& elements);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static QList<UiElement> extractElementsFromXml(QXmlStreamReader &xmlReader);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // XMLPARSER_H
|
||||||
@ -1,61 +0,0 @@
|
|||||||
#include "adb_fun.h"
|
|
||||||
#include <fstream>
|
|
||||||
#include <iostream>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <sstream>
|
|
||||||
#include <array>
|
|
||||||
#include <ctime>
|
|
||||||
#include <iomanip>
|
|
||||||
|
|
||||||
std::string exec(const std::string& cmd) {
|
|
||||||
std::array<char, 256> 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;
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
#ifndef ADB_FUN_H
|
|
||||||
#define ADB_FUN_H
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
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
|
|
||||||
@ -1,240 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by Misterio on 24.04.2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "script_worker.h"
|
|
||||||
#include "adb_fun.h"
|
|
||||||
#include "xml_reader.h"
|
|
||||||
#include <regex>
|
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
// Вспомогательные функции
|
|
||||||
|
|
||||||
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<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)) {
|
|
||||||
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<UiElement>& ui) {
|
|
||||||
for (const auto& el : ui) {
|
|
||||||
if (el.text == "Введите ПИН") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<UiElement> getPinButtons(const std::vector<UiElement>& ui) {
|
|
||||||
std::vector<UiElement> 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<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 {};
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by Misterio on 24.04.2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef SCRIPT_WORKER_H
|
|
||||||
#define SCRIPT_WORKER_H
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
|
||||||
#include "xml_reader.h"
|
|
||||||
|
|
||||||
// Функция для запуска скрипта с передачей параметров
|
|
||||||
void runScriptWorker(const std::string& pin, const std::string& last4digits);
|
|
||||||
|
|
||||||
// Функции для определения различных экранов и действий
|
|
||||||
|
|
||||||
// Проверка, что перед нами экран логина
|
|
||||||
bool isLoginScreen(const std::vector<UiElement>& ui);
|
|
||||||
|
|
||||||
// Проверка, что перед нами экран ввода ПИН
|
|
||||||
bool isPinScreen(const std::vector<UiElement>& ui);
|
|
||||||
|
|
||||||
// Получение списка кнопок с цифрами для ввода ПИН
|
|
||||||
std::vector<UiElement> getPinButtons(const std::vector<UiElement>& ui);
|
|
||||||
|
|
||||||
// Тап по элементу с текстом
|
|
||||||
void tapByText(const std::vector<UiElement>& ui, const std::string& text);
|
|
||||||
|
|
||||||
// Тап по полю ввода
|
|
||||||
void tapInputField(const std::vector<UiElement>& ui, const std::string& fieldText);
|
|
||||||
|
|
||||||
// Тап по кнопке "Далее" и её вариациям
|
|
||||||
void tapNextButton(const std::vector<UiElement>& ui);
|
|
||||||
|
|
||||||
// Обработка экрана с пост-логин баннерами или диалогами
|
|
||||||
bool handlePostLoginScreen(const std::vector<UiElement>& ui);
|
|
||||||
|
|
||||||
// Проверка, что перед нами главный экран
|
|
||||||
bool isMainScreen(const std::vector<UiElement>& ui);
|
|
||||||
|
|
||||||
// Тап по главному экрану
|
|
||||||
void tapMainScreen(const std::vector<UiElement>& ui);
|
|
||||||
|
|
||||||
// Функция для поиска карты по последним 4 цифрам
|
|
||||||
UiElement findCardByLast4Digits(const std::vector<UiElement>& ui, const std::string& last4digits);
|
|
||||||
|
|
||||||
// Поиск кнопки "Оплатить" на экране
|
|
||||||
UiElement findPayButton(const std::vector<UiElement>& ui);
|
|
||||||
|
|
||||||
#endif //SCRIPT_WORKER_H
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
#include "xml_reader.h"
|
|
||||||
|
|
||||||
#include <QFile>
|
|
||||||
#include <QXmlStreamReader>
|
|
||||||
#include <QRegularExpression>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <regex>
|
|
||||||
|
|
||||||
|
|
||||||
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<UiElement> parseUiDumpUsingQXml(const QString& filePath) {
|
|
||||||
std::vector<UiElement> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Пропускаем закрывающий тег </node>
|
|
||||||
if (xmlReader.isEndElement()) {
|
|
||||||
qDebug() << "End element:" << xmlReader.name();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xmlReader.hasError()) {
|
|
||||||
qWarning() << "Final XML parsing error:" << xmlReader.errorString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return uiElements;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
#ifndef XML_READER_H
|
|
||||||
#define XML_READER_H
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
struct UiElement {
|
|
||||||
int x1, y1, x2, y2;
|
|
||||||
QString text;
|
|
||||||
QString resourceId;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<UiElement> parseUiDumpUsingQXml(const QString &filePath);
|
|
||||||
bool dumpAndPullUiXml(const std::string& localPath);
|
|
||||||
|
|
||||||
|
|
||||||
#endif //XML_READER_H
|
|
||||||
29
main.cpp
29
main.cpp
@ -8,7 +8,7 @@
|
|||||||
#include "database/dao/CommonDataDAO.h"
|
#include "database/dao/CommonDataDAO.h"
|
||||||
#include "db/AccountInfoScreener.h"
|
#include "db/AccountInfoScreener.h"
|
||||||
#include "services/DeviceScreener.h"
|
#include "services/DeviceScreener.h"
|
||||||
#include "automation_script/script_worker.h"
|
#include "automation_script/Script.h"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
@ -45,11 +45,31 @@ void setupWorkerAndThread(QCoreApplication &app) {
|
|||||||
thread->start();
|
thread->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setupScriptAutoPaymentAndThread(QCoreApplication &app) {
|
||||||
|
auto *thread = new QThread;
|
||||||
|
auto *scriptWorker = new Script;
|
||||||
|
|
||||||
|
// Перемещаем worker в новый поток thread.
|
||||||
|
// Это значит, что все слоты worker, вызываемые через connect(), теперь будут исполняться в этом фоновом потоке.
|
||||||
|
scriptWorker->moveToThread(thread);
|
||||||
|
// Когда поток стартует, запускаем start()
|
||||||
|
QObject::connect(thread, &QThread::started, scriptWorker, &Script::start);
|
||||||
|
// Когда работа (worker) завершена (emit finished()), останавливаем поток
|
||||||
|
QObject::connect(scriptWorker, &Script::finished, thread, &QThread::quit);
|
||||||
|
// Удаляем работу (worker) (deleteLater)
|
||||||
|
QObject::connect(scriptWorker, &Script::finished, scriptWorker, &QObject::deleteLater);
|
||||||
|
// Удаляем поток (deleteLater)
|
||||||
|
QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
|
||||||
|
|
||||||
|
QObject::connect(&app, &QCoreApplication::aboutToQuit, [=]() {
|
||||||
|
scriptWorker->stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
thread->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
runScriptWorker("1211", "0823"); // Просто запускаем script
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
|
|
||||||
// QList<Card> cards = AccountInfoScreener::parseCardData(QByteArray());
|
// QList<Card> cards = AccountInfoScreener::parseCardData(QByteArray());
|
||||||
@ -65,6 +85,7 @@ int main(int argc, char *argv[]) {
|
|||||||
// qDebug() << "Account info: " << AccountDAO::upsertAccount(info);
|
// qDebug() << "Account info: " << AccountDAO::upsertAccount(info);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// setupScriptAutoPaymentAndThread(app);
|
||||||
|
|
||||||
// setupWorkerAndThread(app);
|
// setupWorkerAndThread(app);
|
||||||
//
|
//
|
||||||
|
|||||||
47
models/auto_payment/UiElement.cpp
Normal file
47
models/auto_payment/UiElement.cpp
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
//
|
||||||
|
// Created by AmirS on 26.04.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "UiElement.h"
|
||||||
|
#include <QXmlStreamReader>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
UiElement::UiElement(const int x1, const int y1, const int x2, const int y2, QString text, QString className,
|
||||||
|
const bool clickable)
|
||||||
|
: m_x1(x1), m_y1(y1), m_x2(x2), m_y2(y2), m_text(std::move(text)), m_className(std::move(className)),
|
||||||
|
m_clickable(clickable) {
|
||||||
|
}
|
||||||
|
|
||||||
|
static const QRegularExpression boundsRegex(R"(\[([0-9]+),([0-9]+)\]\[([0-9]+),([0-9]+)\])");
|
||||||
|
|
||||||
|
UiElement UiElement::fromXmlNode(const QXmlStreamReader &xmlReader) {
|
||||||
|
int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
|
||||||
|
QString text, className;
|
||||||
|
bool clickable = false;
|
||||||
|
|
||||||
|
if (xmlReader.attributes().hasAttribute("bounds")) {
|
||||||
|
QString bounds = xmlReader.attributes().value("bounds").toString();
|
||||||
|
QRegularExpressionMatch match = boundsRegex.match(bounds);
|
||||||
|
if (match.hasMatch()) {
|
||||||
|
x1 = match.captured(1).toInt();
|
||||||
|
y1 = match.captured(2).toInt();
|
||||||
|
x2 = match.captured(3).toInt();
|
||||||
|
y2 = match.captured(4).toInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xmlReader.attributes().hasAttribute("text")) {
|
||||||
|
text = xmlReader.attributes().value("text").toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xmlReader.attributes().hasAttribute("class")) {
|
||||||
|
className = xmlReader.attributes().value("class").toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xmlReader.attributes().hasAttribute("clickable")) {
|
||||||
|
clickable = xmlReader.attributes().value("clickable").toString() == "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
return UiElement(x1, y1, x2, y2, text, className, clickable);
|
||||||
|
}
|
||||||
32
models/auto_payment/UiElement.h
Normal file
32
models/auto_payment/UiElement.h
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// Created by AmirS on 26.04.2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef UIELEMENT_H
|
||||||
|
#define UIELEMENT_H
|
||||||
|
|
||||||
|
#include <QXmlStreamReader>
|
||||||
|
|
||||||
|
class UiElement {
|
||||||
|
public:
|
||||||
|
explicit UiElement(int x1 = 0, int y1 = 0, int x2 = 0, int y2 = 0,
|
||||||
|
QString text = "", QString className = "", bool clickable = false);
|
||||||
|
|
||||||
|
[[nodiscard]] int x1() const { return m_x1; }
|
||||||
|
[[nodiscard]] int y1() const { return m_y1; }
|
||||||
|
[[nodiscard]] int x2() const { return m_x2; }
|
||||||
|
[[nodiscard]] int y2() const { return m_y2; }
|
||||||
|
[[nodiscard]] QString text() const { return m_text; }
|
||||||
|
[[nodiscard]] QString className() const { return m_className; }
|
||||||
|
[[nodiscard]] bool isClickable() const { return m_clickable; }
|
||||||
|
|
||||||
|
static UiElement fromXmlNode(const QXmlStreamReader &xmlReader);
|
||||||
|
|
||||||
|
private:
|
||||||
|
int m_x1, m_y1, m_x2, m_y2;
|
||||||
|
QString m_text;
|
||||||
|
QString m_className;
|
||||||
|
bool m_clickable;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // UIELEMENT_H
|
||||||
Loading…
Reference in New Issue
Block a user