1
0
forked from BRT/arc
arc/android/rshb/HomeScreenScript.cpp
2026-02-22 20:48:16 +07:00

513 lines
23 KiB
C++
Raw Permalink 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.

#include "HomeScreenScript.h"
#include <QThread>
#include <QDebug>
#include <QProcess>
#include <QRandomGenerator>
#include "ScreenXmlParser.h"
#include "adb/AdbUtils.h"
namespace Rshb {
HomeScreenScript::HomeScreenScript(QObject *parent)
: QObject(parent) {
}
HomeScreenScript::~HomeScreenScript() = default;
bool HomeScreenScript::goToHomeScreen(
const QString &deviceId,
const QString &packageName,
const QString &pinCode,
const int width,
const int height
) {
// Запустить устройство
if (const QString pid = AdbUtils::tryToGetRunningAppPid(deviceId, packageName); !pid.isEmpty()) {
qDebug() << "Process already running, pid: " << pid;
AdbUtils::getScreenDumpAsXml(deviceId, 1000);
AdbUtils::tryToKillApplication(deviceId, packageName);
}
if (!AdbUtils::tryToStartApplication(deviceId, packageName)) {
qWarning() << "Process has not started";
return false;
}
xmlScreenParser.parseAndSaveXml(
AdbUtils::getScreenDumpAsXml(deviceId, 0)
);
// Даем минимальное время на запуск
QThread::msleep(4500);
int counter = 0;
int restarted = 0;
int totalCounter = 0;
bool findAndInputPincode = false;
while (true) {
counter++;
totalCounter++;
// 3 раза уже перезапускали приложение, завершаем попытки
if (restarted > 2) {
return false;
}
// Если более 30 секунд мы пытаемся перейти на домашнюю, перезапускаем
if (counter > 30) {
AdbUtils::tryToKillApplication(deviceId, packageName);
QThread::msleep(4500);
++restarted;
counter = 0;
}
const QString xml = AdbUtils::getScreenDumpAsXml(deviceId, totalCounter + 1);
xmlScreenParser.parseAndSaveXml(xml);
qDebug() << "Created xml+screenshot, idx: " << (totalCounter + 1);
if (xmlScreenParser.isHomeScreen()) {
qDebug() << "isHomeScreen!!!!!!!";
return true;
}
// Проверяем на банеры после пин-кодом
Node closeBannerOrDialogNode = xmlScreenParser.tryToFindBannerOrDialog(width, height);
if (closeBannerOrDialogNode.x() != -1 && closeBannerOrDialogNode.y() != -1) {
qDebug() << "--- tryToFindBannerOrDialog";
AdbUtils::makeTap(deviceId, closeBannerOrDialogNode.x(), closeBannerOrDialogNode.y());
QThread::msleep(1000);
continue;
}
// Проверяем на банеры перед пин-кодом
Node closeBannerNode = xmlScreenParser.tryToFindSecurityBanner();
if (!closeBannerNode.isEmpty()) {
qDebug() << "--- tryToFindSecurityBanner";
AdbUtils::makeTap(deviceId, closeBannerNode.x(), closeBannerNode.y());
QThread::msleep(1000);
continue;
}
// Проверяем, что это экран ввода пин-кода
if (!findAndInputPincode) {
if (const QList<Node> pincode = xmlScreenParser.tryToFindPinCode(pinCode, "Введите ПИН");
pincode.size() == 4) {
qDebug() << "--- tryToFindPinCode";
// делаем 4 тапа и ждем секунду и продолжаем
for (const Node &node: pincode) {
AdbUtils::makeTap(deviceId, node.x(), node.y());
QThread::msleep(QRandomGenerator::global()->bounded(200, 901));
}
// После ввода пин-код нужно сделать задержку, пока подгрузка идет, чтобы снова не спарсить
findAndInputPincode = true;
QThread::msleep(3000);
continue;
}
}
QThread::msleep(1000);
// Если что-то пойдет не по плану...
if (totalCounter > 100) {
return false;
}
}
}
QString getPriceAsString(const double value) {
QString sign = value < 0 ? "Минус" : "Плюс";
const auto intValue = static_cast<qint64>(std::abs(value)); // округление до целого
QString number = QString::number(intValue);
QString formatted;
int count = 0;
for (int i = number.length() - 1; i >= 0; --i) {
formatted.prepend(number[i]);
++count;
if (count % 3 == 0 && i != 0) {
formatted.prepend(' ');
}
}
return QString("%1 %2").arg(sign, formatted);
}
Node swipeToButton(
ScreenXmlParser &xmlScreenParser, const QString &deviceId,
const QString &text, int &counter, const int width, const int height
) {
for (int i = 0; i < 10; ++i) {
if (Node button = xmlScreenParser.findButtonNode(text, true); button.x() > 0 && button.y() > 0) {
return button;
}
const int x = width / 2;
const int offset = height / 4;
AdbUtils::makeSwipe(deviceId, x, height - offset, x, offset);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter));
}
return {};
}
void HomeScreenScript::start() {
// Достаем данные из БД по ИД транзакции, перед тем как запустить мы кладем в БД все данные
const QString deviceId = "6edc4a47";
const QString packageName = "ru.rshb.dbo";
const QString pinCode = "1102";
const int width = 720;
const int height = 1600;
const QString cardNumber = "7 1 8 3";
// В том числе и данные на каком устройстве это делать
// TODO тут код работы с БД
// +7 (964) 181-51-46
const QString phoneNumber = "+79641815146";
const QString bankName = "Альфа-Банк";
const QString paySum = "10";
int index = 0;
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++index));
// xmlScreenParser.test();
// тут просто получаем инфу которую спарсил
// == 1. Входящий (TransactionType::Phone)
// Перевод через СБП от Егор Иванович Ж (+7(000)-000-00-87) из Озон Банк (Ozon) на 40817810328000022077 .
// Идентификатор операции в СБП С2С B5121111443333300170011491301 , Дата операции 01.05.2025 14:14:41 Статус перевода Исполнен
// == 2. Исходящий (TransactionType::Phone)
// Перевод Надежда Иванкина К** в Сбербанк через СБП, по номеру телефона +79000810046, ID перевода 445900141, дата 03.05.2025
// (TransactionType::Card) -> T-BANK CARD2CARD
// TransactionInfo trInfo = xmlScreenParser.parseTransactionInfoHistory(width, height);
// qDebug().noquote().nospace() << convertTransactionToString(trInfo);
// Если начинается с ЕСПП - то не кликаем еще, ждет обработку
// А все переводы по карте - они не начинаются со слова "Перевод" ->
// .. пробуем кликать, но если не кликается ???
QList<TransactionInfo> transactions = xmlScreenParser.parseTodayAndYesterdayHistory();
for (const TransactionInfo &info: transactions) {
QString text = QString("%1 %2").arg(info.description, getPriceAsString(info.amount));
qDebug().noquote().nospace() << convertTransactionToString(info);
// Node trButton = xmlScreenParser.findButtonNode(text, true);
Node trButton = swipeToButton(xmlScreenParser, deviceId, text, index, width, height);
if (!trButton.isEmpty()) {
AdbUtils::makeTap(deviceId, trButton.x(), trButton.y());
QThread::msleep(500);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++index));
Node moreDetailBtn = xmlScreenParser.findButtonNode("Детали операции", false);
if (!moreDetailBtn.isEmpty()) {
AdbUtils::makeTap(deviceId, moreDetailBtn.x(), moreDetailBtn.y());
QThread::msleep(200);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++index));
TransactionInfo info = xmlScreenParser.parseTransactionInfoHistory(width, height);
qDebug().noquote().nospace() << convertTransactionToString(info);
}
AdbUtils::goBack(deviceId);
} else {
qWarning() << "--- trButton not found..........";
}
// Перевод ВЛАДИСЛАВ НИКОЛАЕВИЧ К** в Т-Банк через СБП, по номеру телефона +7985553... Минус 10 рублей 0 копеек
}
return;
int payCounter = 0;
if (false) {
// Оплата со страницы карты
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter));
Node payBtn = xmlScreenParser.findButtonNode("Оплатить", false);
if (!payBtn.isEmpty()) {
AdbUtils::makeTap(deviceId, payBtn.x(), payBtn.y());
QThread::msleep(1000);
} else {
qWarning() << "--- payBtn not found";
}
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter));
// TODO - По номеру карты В РСХБ и другие банки
Node payTitle = xmlScreenParser.findTextNode("Платежи и переводы");
if (!payTitle.isEmpty() && payTitle.y() < 300) {
Node payByPhoneBtn = xmlScreenParser.findButtonNode("По номеру телефона В РСХБ и через", false);
if (!payByPhoneBtn.isEmpty()) {
AdbUtils::makeTap(deviceId, payByPhoneBtn.x(), payByPhoneBtn.y());
QThread::msleep(1000);
} else {
qWarning() << "--- payByPhoneBtn not found";
}
} else {
qWarning() << "--- payTitle not found";
}
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter));
Node payByPhoneTitle = xmlScreenParser.findTextNode("Перевод по телефону");
Node continueBtn = xmlScreenParser.findButtonNode("Продолжить", false);
if (!payByPhoneTitle.isEmpty() && payByPhoneTitle.y() < 300 && continueBtn.isEmpty()) {
Node inputPhone = xmlScreenParser.findFirstEditText();
AdbUtils::makeTap(deviceId, inputPhone.x(), inputPhone.y());
QThread::msleep(500);
AdbUtils::inputText(deviceId, phoneNumber);
QThread::msleep(1000);
} else {
qWarning() << "--- payByPhoneTitle not found (no continueBtn)";
}
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter));
QString payBankName;
payBankName = bankName == "Россельхозбанк" ? "Россельхозбанк" : "Другой банк через СБП";
Node payOtherBankBtn = xmlScreenParser.findButtonNode(payBankName, false);
if (!payOtherBankBtn.isEmpty()) {
AdbUtils::makeTap(deviceId, payOtherBankBtn.x(), payOtherBankBtn.y());
QThread::msleep(1000);
} else {
qWarning() << "--- payOtherBankBtn not found";
}
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter));
for (int i = 0; i < 5; ++i) {
Node allBanksTitle = xmlScreenParser.findTextNode("Банки участники");
if (!allBanksTitle.isEmpty() && allBanksTitle.y() < 300) {
Node inputBankName = xmlScreenParser.findFirstEditText();
AdbUtils::makeTap(deviceId, inputBankName.x(), inputBankName.y());
QThread::msleep(500);
AdbUtils::enableAdbIMEKeyboard(deviceId);
AdbUtils::inputAdbIMEText(deviceId, bankName);
AdbUtils::disableAdbIMEKeyboard(deviceId);
QThread::msleep(1000);
break;
} else {
qWarning() << "--- allBanksTitle not found, i: " << i;
}
QThread::msleep(1000);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter));
}
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter));
for (int i = 0; i < 5; ++i) {
Node bankNameListItem = xmlScreenParser.findTextNode(bankName);
if (!bankNameListItem.isEmpty()) {
AdbUtils::makeTap(deviceId, bankNameListItem.x(), bankNameListItem.y());
QThread::msleep(1000);
break;
} else {
qWarning() << "--- bankNameListItem not found, i: " << i;
}
QThread::msleep(1000);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter));
}
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter));
Node payByPhoneScreenTitle = xmlScreenParser.findTextNode("Перевод по телефону");
Node continuePayBtn = xmlScreenParser.findButtonNode("Продолжить", false);
if (!payByPhoneScreenTitle.isEmpty() && payByPhoneScreenTitle.y() < 300 && !continuePayBtn.isEmpty()) {
Node sumBtn = xmlScreenParser.findButtonNode("0", false);
if (!sumBtn.isEmpty()) {
AdbUtils::makeTap(deviceId, sumBtn.x(), sumBtn.y());
QThread::msleep(500);
AdbUtils::inputText(deviceId, paySum);
QThread::msleep(500);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter));
Node readyBtn = xmlScreenParser.findTextNode("Готово");
if (!readyBtn.isEmpty()) {
AdbUtils::makeTap(deviceId, readyBtn.x(), readyBtn.y());
QThread::msleep(500);
AdbUtils::makeTap(deviceId, continuePayBtn.x(), continuePayBtn.y());
QThread::msleep(1000);
} else {
qWarning() << "--- readyBtn not found";
}
} else {
qWarning() << "--- sumBtn not found";
}
} else {
qWarning() << "--- payByPhoneScreenTitle not found";
}
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter));
for (int i = 0; i < 5; ++i) {
// TODO кнопка содержит "Перевести 10 ₽" если будет от 1000 то скорее всвего будет "Перевести 1 000 ₽",
// TODO не ясно как форматирование будет, поэтому пока на начало зашьем
Node makePayBtn = xmlScreenParser.findButtonNode("Перевести ", true);
if (!makePayBtn.isEmpty()) {
AdbUtils::makeTap(deviceId, makePayBtn.x(), makePayBtn.y());
QThread::msleep(500);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter));
if (const QList<Node> pincode = xmlScreenParser.tryToFindPinCode(pinCode, "Введите код доступа");
pincode.size() == 4) {
// FIXME вынести в отдельный метод
// делаем 4 тапа и ждем секунду и продолжаем
for (const Node &node: pincode) {
AdbUtils::makeTap(deviceId, node.x(), node.y());
QThread::msleep(QRandomGenerator::global()->bounded(200, 901));
}
// После ввода пин-кода, ждем 3с
QThread::msleep(3000);
}
break;
} else {
qWarning() << "--- makePayBtn not found, i: " << i;
}
QThread::msleep(1000);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter));
}
}
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter));
for (int i = 0; i < 5; ++i) {
Node moreDetailBtn = xmlScreenParser.findButtonNode("Детали операции", false);
if (!moreDetailBtn.isEmpty()) {
AdbUtils::makeTap(deviceId, moreDetailBtn.x(), moreDetailBtn.y());
QThread::msleep(200);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter));
Node paymentCompleted = xmlScreenParser.findTextNode("Исполнен", 0, 0, width, height / 2);
Node paymentInProgress = xmlScreenParser.findTextNode("Выполняется", 0, 0, width, height / 2);
if (!paymentCompleted.isEmpty()) {
// Если вдруг исполнен
} else if (!paymentInProgress.isEmpty()) {
// Если все еще в ожидании
} else {
qWarning() << "--- paymentCompleted or paymentInProgress not found...";
}
break;
} else {
qWarning() << "--- moreDetailBtn not found, i: " << i;
}
QThread::msleep(1000);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++payCounter));
}
return;
int counter = 0;
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, counter));
Node closeBannerOrDialogNode = xmlScreenParser.tryToFindBannerOrDialog(width, height);
if (closeBannerOrDialogNode.x() != -1 && closeBannerOrDialogNode.y() != -1) {
qDebug() << "--- tryToFindBannerOrDialog";
AdbUtils::makeTap(deviceId, closeBannerOrDialogNode.x(), closeBannerOrDialogNode.y());
QThread::msleep(1000);
}
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter));
if (xmlScreenParser.isHomeScreen()) {
qDebug() << "--- isHomeScreen";
if (Node allCardTitle = xmlScreenParser.findTextNode("Счета и карты"); !allCardTitle.isEmpty()) {
// 1. Если нет кнопки видимой, свайпаем. Кликаем на "Все продукты"
for (int i = 0; i < 5; ++i) {
Node allProductsBtn = xmlScreenParser.findButtonNode("Все продукты", false);
// Node allProductsBtn = xmlScreenParser.findTextNode("Курсы валют и металлов"); для теста
if (allProductsBtn.x() > 0 && allProductsBtn.y() > 0) {
qDebug() << "--- All Product in focus";
AdbUtils::makeTap(deviceId, allProductsBtn.x(), allProductsBtn.y());
QThread::msleep(1000);
break;
}
AdbUtils::makeSwipe(deviceId,
allCardTitle.x(), allCardTitle.y(),
allCardTitle.x(), allCardTitle.y() - 200);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter));
}
for (int i = 0; i < 5; ++i) {
Node allProductsTitle = xmlScreenParser.findTextNode("Все продукты");
// Проверяем что вверху заголовок
if (!allProductsTitle.isEmpty() && allProductsTitle.y() < 300) {
qDebug() << "--- All Product screen";
// TODO тут делаем синхронизацию данных все карт
Node cardNumberBtn = xmlScreenParser.findButtonNode(cardNumber, true);
AdbUtils::makeTap(deviceId, cardNumberBtn.x(), cardNumberBtn.y());
break;
}
QThread::msleep(1000);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter));
}
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter));
Node filterBtn = xmlScreenParser.findButtonNode("РСХБ Онлайн", false);
if (!filterBtn.isEmpty()) {
AdbUtils::makeTap(deviceId, filterBtn.x(), filterBtn.y());
QThread::msleep(1000);
} else {
qWarning() << "--- filterBtn not found";
}
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter));
Node allTransactionFilerBtn = xmlScreenParser.findButtonNode("Все операции", false);
if (!allTransactionFilerBtn.isEmpty()) {
AdbUtils::makeTap(deviceId, allTransactionFilerBtn.x(), allTransactionFilerBtn.y());
QThread::msleep(1000);
} else {
qWarning() << "--- allTransactionFilerBtn not found";
}
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter));
for (int i = 0; i < 5; ++i) {
QList<Node> transactions = xmlScreenParser.getLast2DaysTransactions();
if (!transactions.empty()) {
for (const Node &transaction: transactions) {
qDebug() << transaction.text;
// TODO вот мы дошли до списка транзакций
}
break;
}
QThread::msleep(1000);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter));
}
//
} else {
// пишем в БД, что не дошли до домашней страницы, и завершаемся
// сохраняем скрин приложения
// дделаем алерт
}
} else {
// пишем в БД, что не дошли до домашней страницы, и завершаемся
// сохраняем скрин приложения
// дделаем алерт
}
return;
// 1. Идем на домашнюю страницу
if (goToHomeScreen(deviceId, packageName, pinCode, width, height)) {
// мы на главном
// перед тем как юзать снова проверить на банеры
// Проверяем на банеры после пин-кодом
// Это уже для шага дальше
// если нулевые координаты, свайпаем вверх, чтобы текст "Счета и карты" был у верхнего края
} else {
// пишем в БД, что не дошли до домашней страницы, и завершаемся
// сохраняем скрин приложения
// дделаем алерт
}
// 2. Идем дальше
emit finished();
}
void HomeScreenScript::stop() {
m_running = false;
}
} // namespace Rshb