1
0
forked from BRT/arc

Enhance error handling, improve UI parsing logic, and refine tap retry workflows across Ozon scripts

This commit is contained in:
slava 2026-03-23 21:30:37 +07:00
parent 01f8801a73
commit b42fb5eea9
6 changed files with 127 additions and 19 deletions

View File

@ -109,6 +109,25 @@ void GetCardInfoScript::doStart() {
qDebug() << "[GetCardInfo] Profile info already in DB or username not found, skipping profile parse";
}
// Проверяем что на домашнем экране перед поиском карт
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
if (!xmlScreenParser.isHomeScreen()) {
qDebug() << "[GetCardInfo] Not on home screen before card search, restarting...";
if (!runAppAndGoToHomeScreen(device.id, app.package, app.pinCode,
device.screenWidth, device.screenHeight)) {
m_error = "Cannot reach home screen before card search: " + m_deviceId;
qWarning() << m_error;
AppLogger::log("ozon/GetCardInfo", m_deviceId, m_error,
AdbUtils::captureScreenshotBytes(m_deviceId),
"FETCH_PROFILE");
AdbUtils::tryToKillApplication(m_deviceId, app.package);
emit finishedWithResult(m_error);
return;
}
tryToCloseAllBannersOnHomePage(m_deviceId, device.screenWidth, device.screenHeight);
QThread::msleep(500);
}
// Ждём пока все карты подгрузятся — до 5 попыток
QList<AccountInfo> cards;
for (int attempt = 0; attempt < 5; ++attempt) {
@ -194,7 +213,17 @@ void GetCardInfoScript::doStart() {
if (freshApp.bankProfileId.isEmpty()) {
ns.addExtra("app_id", freshApp.id);
ns.postBankProfile();
qDebug() << "[GetCardInfo] postBankProfile done";
// Проверяем что bankProfileId сохранился
const ApplicationInfo afterPost = ApplicationDAO::getApplication(m_deviceId, m_appCode);
if (afterPost.bankProfileId.isEmpty()) {
const QString err = "postBankProfile failed for " + m_deviceId;
qWarning() << "[GetCardInfo]" << err;
AppLogger::log("ozon/GetCardInfo", m_deviceId, err,
AdbUtils::captureScreenshotBytes(m_deviceId),
"FETCH_PROFILE");
} else {
qDebug() << "[GetCardInfo] postBankProfile done";
}
} else {
ns.addExtra("bank_profile_id", freshApp.bankProfileId);
ns.patchBankProfile();
@ -227,16 +256,24 @@ void GetCardInfoScript::doStart() {
AdbUtils::makeTap(m_deviceId, cardBtn.x(), cardBtn.y());
// Ждём экран карты — ищем "Реквизиты карты" в XML дампе
// Ждём экран карты — ищем кнопку "Показать"
Node showBtn;
for (int attempt = 0; attempt < 10; ++attempt) {
QThread::msleep(2000);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
Node reqNode = xmlScreenParser.findTextNode("Реквизиты карты");
if (!reqNode.isEmpty()) {
showBtn = xmlScreenParser.findButtonNode("Показать", false);
if (!showBtn.isEmpty()) break;
showBtn = xmlScreenParser.findButtonNode("Показать", false);
if (!showBtn.isEmpty()) break;
// Если всё ещё на домашнем экране — тап не сработал, повторяем
if (xmlScreenParser.isHomeScreen()) {
cardBtn = xmlScreenParser.findCardButtonByLastNumbers(card.lastNumbers);
if (!cardBtn.isEmpty()) {
qDebug() << "[GetCardInfo] Still on home screen, re-tapping card" << card.lastNumbers;
AdbUtils::makeTap(m_deviceId, cardBtn.x(), cardBtn.y());
}
}
qDebug() << "[GetCardInfo] Card screen not loaded, attempt" << attempt + 1 << "/ 10";
}
@ -277,7 +314,26 @@ void GetCardInfoScript::doStart() {
continue;
}
qDebug() << "[GetCardInfo] Card number not visible, attempt" << attempt + 1 << "/ 10";
// "Подождите" — загрузка в процессе, просто ждём
if (!xmlScreenParser.findButtonNode("Подождите", false).isEmpty()) {
qDebug() << "[GetCardInfo] 'Подождите' visible, waiting... attempt" << attempt + 1 << "/ 10";
continue;
}
// "Скрыть" видна — номер должен быть раскрыт, но парсер не нашёл
if (!xmlScreenParser.findButtonNode("Скрыть", false).isEmpty()) {
qDebug() << "[GetCardInfo] 'Скрыть' visible but card number not parsed, attempt" << attempt + 1 << "/ 10";
continue;
}
// Повторно нажимаем "Показать" если тап не сработал
Node retryShowBtn = xmlScreenParser.findButtonNode("Показать", false);
if (!retryShowBtn.isEmpty()) {
qDebug() << "[GetCardInfo] Re-tapping 'Показать', attempt" << attempt + 1 << "/ 10";
AdbUtils::makeTap(m_deviceId, retryShowBtn.x(), retryShowBtn.y());
} else {
qDebug() << "[GetCardInfo] Card number not visible, attempt" << attempt + 1 << "/ 10";
}
}
if (!cardNumber.isEmpty()) {
@ -288,7 +344,16 @@ void GetCardInfoScript::doStart() {
AccountDAO::updateCardNumber(saved.id, cardNumber);
}
} else {
qWarning() << "[GetCardInfo] Card number not found for" << card.lastNumbers;
const QString err = "Card number not found for " + card.lastNumbers;
qWarning() << "[GetCardInfo]" << err;
AccountInfo saved = AccountDAO::findAppAccount(m_deviceId, m_appCode, card.lastNumbers);
if (saved.id != -1) {
AccountDAO::updateAccountById(saved.id, accountStatusToString(AccountStatus::Off),
saved.description, saved.currency);
}
AppLogger::log("ozon/GetCardInfo", m_deviceId, err,
AdbUtils::captureScreenshotBytes(m_deviceId),
"FETCH_PROFILE");
}
// Возвращаемся на домашний экран для следующей карты
@ -314,6 +379,7 @@ void GetCardInfoScript::doStart() {
materialBody["bank_profile_id"] = updatedApp.bankProfileId;
materialBody["card_number"] = dbCard.cardNumber;
materialBody["name"] = updatedApp.fullName;
materialBody["state"] = accountStatusToString(dbCard.status) == "active" ? "enabled" : "disabled";
ns.setPayload(QJsonDocument(materialBody).toJson());
ns.addExtra("account_id", dbCard.id);
ns.postMaterial();

View File

@ -239,6 +239,13 @@ void GetLastTransactionsScript::doStart() {
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
detail = xmlScreenParser.parseTransactionDetail();
if (!detail.bankTime.isEmpty()) break;
// Если всё ещё на экране операций — тап не сработал, повторяем
if (xmlScreenParser.isTransactionListScreen()) {
qDebug() << "[GetLastTransactions] Still on transaction list, re-tapping" << i + 1;
AdbUtils::makeTap(m_deviceId, visibleBtn.x(), visibleBtn.y());
}
qDebug() << "[GetLastTransactions] Detail not loaded, attempt" << attempt + 1 << "/ 10";
QThread::msleep(1500);
}

View File

@ -11,6 +11,7 @@
#include "dao/ApplicationDAO.h"
#include "dao/DeviceDAO.h"
#include "net/NetworkService.h"
#include "AppLogger.h"
namespace Ozon {
@ -141,7 +142,16 @@ void GetProfileInfoScript::doStart() {
if (freshApp.bankProfileId.isEmpty()) {
ns.addExtra("app_id", freshApp.id);
ns.postBankProfile();
qDebug() << "[GetProfileInfo] postBankProfile done";
const ApplicationInfo afterPost = ApplicationDAO::getApplication(m_deviceId, m_appCode);
if (afterPost.bankProfileId.isEmpty()) {
const QString err = "postBankProfile failed for " + m_deviceId;
qWarning() << "[GetProfileInfo]" << err;
AppLogger::log("ozon/GetProfileInfo", m_deviceId, err,
AdbUtils::captureScreenshotBytes(m_deviceId),
"FETCH_PROFILE");
} else {
qDebug() << "[GetProfileInfo] postBankProfile done";
}
} else {
ns.addExtra("bank_profile_id", freshApp.bankProfileId);
ns.patchBankProfile();

View File

@ -121,7 +121,7 @@ void PayByCardScript::doStart() {
// Закрываем баннер "Заказать карту" — тапаем сверху экрана
if (Node orderCardNode = xmlScreenParser.findTextNode("Заказать карту"); !orderCardNode.isEmpty()) {
qDebug() << "[PayByCard] 'Заказать карту' banner detected, closing...";
AdbUtils::makeTap(device.id, device.screenWidth / 2, 100);
AdbUtils::makeTap(device.id, device.screenWidth / 2, device.screenHeight / 20);
QThread::msleep(1000);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter));
}

View File

@ -122,7 +122,7 @@ void PayByPhoneScript::doStart() {
// Закрываем баннер "Заказать карту" — тапаем сверху экрана
if (Node orderCardNode = xmlScreenParser.findTextNode("Заказать карту"); !orderCardNode.isEmpty()) {
qDebug() << "[PayByPhone] 'Заказать карту' banner detected, closing...";
AdbUtils::makeTap(device.id, device.screenWidth / 2, 100);
AdbUtils::makeTap(device.id, device.screenWidth / 2, device.screenHeight / 20);
QThread::msleep(1000);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter));
}
@ -223,7 +223,7 @@ void PayByPhoneScript::makePayment(
// Проверяем: уже на экране суммы?
Node sumTitle = xmlScreenParser.findTextNode("По номеру телефона");
if (!sumTitle.isEmpty() && sumTitle.y() < 300) {
if (!sumTitle.isEmpty() && sumTitle.y() < height / 7) {
sumScreenLoaded = true;
break;
}

View File

@ -272,8 +272,8 @@ Node ScreenXmlParser::findUserNameOnHomeScreen(const int screenHeight) {
}
QString ScreenXmlParser::parseProfileFullName() {
// Полное имя — TextView ниже зоны заголовка, выше строки с телефоном.
// В profile.xml это единственный короткий нейтральный TextView в диапазоне y ≈ 400750.
// Полное имя — первый TextView ниже заголовка "Аккаунт",
// который не является известным элементом UI и не содержит спецсимволов.
static const QSet<QString> uiLabels = {
"Аккаунт", "Мои документы", "Паспорт и ИНН",
"Уровень счёта Лимиты и тарифы", "Повысить",
@ -283,11 +283,34 @@ QString ScreenXmlParser::parseProfileFullName() {
"QR-код для пополнений Принимайте чаевые и донаты",
"Добавить Почта"
};
for (const Node &node: m_nodes) {
// Находим y-координату заголовка "Аккаунт"
int titleY = -1;
for (const Node &node : m_nodes) {
if (node.className == "android.widget.TextView"
&& node.text.trimmed() == "Аккаунт"
&& node.width() > 50) {
titleY = node.y();
break;
}
}
if (titleY < 0) return {};
// Находим y-координату блока с телефоном (ограничение снизу)
int phoneY = INT_MAX;
for (const Node &node : m_nodes) {
if (node.text.contains("Номер телефона")) {
phoneY = node.y();
break;
}
}
// Первый подходящий TextView между заголовком и телефоном
for (const Node &node : m_nodes) {
if (node.className == "android.widget.TextView"
&& !node.text.trimmed().isEmpty()
&& node.y() > 400
&& node.y() < 750
&& node.y() > titleY
&& node.y() < phoneY
&& !uiLabels.contains(node.text.trimmed())) {
return node.text.trimmed();
}
@ -1333,12 +1356,14 @@ QList<QPair<AccountInfo, Node> > ScreenXmlParser::parseAccountsInfo() {
}
QString ScreenXmlParser::findFullCardNumber() {
static const QRegularExpression cardNumberRegex(R"(^\d{4}\s\d{4}\s\d{4}\s\d{4}$)");
static const QRegularExpression cardNumberRegex(R"(^\d{4}[\s\x{00A0}\x{202F}]\d{4}[\s\x{00A0}\x{202F}]\d{4}[\s\x{00A0}\x{202F}]\d{4}$)");
for (const Node &node : m_nodes) {
if (node.className == "android.widget.TextView") {
const QString text = node.text.trimmed();
if (cardNumberRegex.match(text).hasMatch()) {
return QString(text).remove(QChar(' '));
QString result = text;
result.remove(QChar(' ')).remove(QChar(0x00A0)).remove(QChar(0x202F));
return result;
}
}
}