1
0
forked from BRT/arc

Refine pin code validation logic, add device timezone support in transaction scripts, and improve XML node logging for error handling.

This commit is contained in:
slava 2026-04-10 17:55:45 +07:00
parent 14ca8348c3
commit 76ceaed0eb
6 changed files with 93 additions and 23 deletions

View File

@ -489,6 +489,14 @@ QSet<QString> AdbUtils::getListApps(const QString &deviceId) {
return apps;
}
QString AdbUtils::getDeviceTimezone(const QString &deviceId) {
QString out;
if (!startProcess(adbPath(), {"-s", deviceId, "shell", "getprop", "persist.sys.timezone"}, &out)) {
return {};
}
return out.trimmed();
}
void AdbUtils::unlockScreen(const QString &deviceId, const int screenWidth, const int screenHeight) {
const QString adb = adbPath();

View File

@ -49,6 +49,9 @@ public:
static QSet<QString> getListApps(const QString &deviceId);
// Возвращает таймзону устройства (напр. "Asia/Bangkok"), либо пустую строку при ошибке
static QString getDeviceTimezone(const QString &deviceId);
private:
static QString adbDir();
static bool startProcess(const QString &program, const QStringList &args,

View File

@ -316,6 +316,36 @@ void GetLastTransactionsScript::doStart() {
return;
}
// Переключаемся на вкладку "Выписка" (по умолчанию открыта "Операции")
Node vypiskaBtn = xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("Выписка"));
if (vypiskaBtn.isEmpty()) {
m_error = "Cannot find 'Выписка' tab: " + m_deviceId;
qWarning() << m_error;
AdbUtils::tryToKillApplication(m_deviceId, app.package);
emit finishedWithResult(m_error);
return;
}
qDebug() << "[Dushanbe::GetLastTransactions] Switching to 'Выписка' tab";
AdbUtils::makeTap(m_deviceId, vypiskaBtn.x(), vypiskaBtn.y());
QThread::msleep(3000);
// Сохраняем дамп Выписки для анализа формата
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
// Закрываем возможный диалог "Ошибка" на Выписке
if (!xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("Ошибка")).isEmpty()) {
Node okBtn = xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("ОК"));
if (!okBtn.isEmpty()) {
qDebug() << "[Dushanbe::GetLastTransactions] Error dialog on Выписка, tapping OK";
AdbUtils::makeTap(m_deviceId, okBtn.x(), okBtn.y());
QThread::msleep(1000);
}
const int x = device.screenWidth / 2;
AdbUtils::makeSwipe(m_deviceId, x, device.screenHeight / 4, x, device.screenHeight * 3 / 4);
QThread::msleep(3000);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
}
// Находим accountId
int accountId = -1;
const QList<MaterialInfo> accounts = MaterialDAO::getAccounts(device.id, m_appCode);
@ -443,9 +473,9 @@ void GetLastTransactionsScript::doStart() {
saveTx.fee = detail.fee;
saveTx.bankName = m_appCode;
saveTx.bankTime = detail.date + " " + detail.time;
saveTx.bankTrExternalId = detail.operationId;
saveTx.bankTrExternalId = "dushanbe_" + detail.date + "_" + detail.time;
saveTx.description = detail.provider;
saveTx.phone = detail.receiverAccount;
saveTx.phone = "";
saveTx.name = detail.receiverAccount;
saveTx.status = detail.status.contains(QString::fromUtf8("Успешн"))
? TransactionStatus::Complete : TransactionStatus::Unknown;

View File

@ -134,8 +134,8 @@ bool CommonScript::runAppAndGoToHomeScreen(
// Проверяем, что это экран ввода пин-кода
if (!findAndInputPincode) {
if (const QList<Node> pincode = xmlScreenParser.tryToFindPinCode(pinCode, "Введите ПИН");
pincode.size() == 4) {
const QList<Node> pincode = xmlScreenParser.tryToFindPinCode(pinCode, "Введите ПИН");
if (pincode.size() == pinCode.length() && pincode.size() >= 4) {
// чтобы пробудить экран
if (Node textPinCodeNode = xmlScreenParser.findTextNode("Введите ПИН"); !textPinCodeNode.isEmpty()) {
AdbUtils::makeTap(deviceId, textPinCodeNode.x(), textPinCodeNode.y());

View File

@ -203,17 +203,21 @@ bool GetLastTransactionsScript::navigateToTransactionList(const DeviceInfo &devi
}
void GetLastTransactionsScript::searchTransaction(const DeviceInfo &device, int accountId) {
const QTimeZone msk("Europe/Moscow");
// Время в Ozon показывается в таймзоне устройства, а не в MSK
const QString tzName = AdbUtils::getDeviceTimezone(m_deviceId);
QTimeZone deviceTz(tzName.toUtf8());
if (!deviceTz.isValid()) deviceTz = QTimeZone("Europe/Moscow");
const int maxScrolls = 30;
const int navBarTop = device.screenHeight - (device.screenHeight / 10);
// Целевая дата в МСК для поиска заголовка дня в списке
const QDateTime searchMsk = m_searchTime.toTimeZone(msk);
const QDate searchDate = searchMsk.date();
// Целевая дата в таймзоне устройства для поиска заголовка дня в списке
const QDateTime searchLocal = m_searchTime.toTimeZone(deviceTz);
const QDate searchDate = searchLocal.date();
qDebug() << "[FetchTransactions] Searching: amount=" << m_searchAmount
<< "time=" << m_searchTime.toString(Qt::ISODate)
<< "date(MSK)=" << searchDate.toString("dd.MM.yyyy");
<< "tz=" << tzName
<< "date(local)=" << searchDate.toString("dd.MM.yyyy");
// ── Шаг 1: скроллим до нужной даты ──────────────────────────────
bool dateFound = false;
@ -231,18 +235,28 @@ void GetLastTransactionsScript::searchTransaction(const DeviceInfo &device, int
{"сентября", 9}, {"октября", 10}, {"ноября", 11}, {"декабря", 12}
};
const QDate today = QDateTime::currentDateTimeUtc().toTimeZone(deviceTz).date();
bool hasOlderDate = false;
for (const Node &node : xmlScreenParser.getNodes()) {
if (node.className != "android.widget.TextView") continue;
const QString t = node.text.trimmed();
// Парсим "4 января, вс" → QDate
QDate headerDate;
// Относительные заголовки "Сегодня" / "Вчера"
if (t.compare("Сегодня", Qt::CaseInsensitive) == 0) {
headerDate = today;
} else if (t.compare("Вчера", Qt::CaseInsensitive) == 0) {
headerDate = today.addDays(-1);
} else {
// Абсолютный формат "4 января, вс"
static const QRegularExpression dateRx(R"((\d{1,2})\s+(\S+),\s+\S+)");
const auto match = dateRx.match(t);
if (!match.hasMatch()) continue;
const int day = match.captured(1).toInt();
const int month = monthMap.value(match.captured(2), 0);
if (month == 0) continue;
const QDate headerDate(searchDate.year(), month, day);
headerDate = QDate(searchDate.year(), month, day);
}
if (!headerDate.isValid()) continue;
if (headerDate == searchDate) {
@ -356,7 +370,7 @@ void GetLastTransactionsScript::searchTransaction(const DeviceInfo &device, int
}
QDateTime txTime = QDateTime::fromString(dtStr, "dd.MM.yyyy, HH:mm");
if (!txTime.isValid()) txTime = QDateTime::fromString(dtStr, "dd.MM.yyyy,HH:mm");
if (txTime.isValid()) txTime.setTimeZone(msk);
if (txTime.isValid()) txTime.setTimeZone(deviceTz);
// Проверяем что дата и время совпадают (±1 час от m_searchTime)
if (txTime.isValid()) {
@ -370,7 +384,7 @@ void GetLastTransactionsScript::searchTransaction(const DeviceInfo &device, int
const qint64 diffSecs = qAbs(txTime.toUTC().secsTo(m_searchTime.toUTC()));
if (diffSecs > 3600) {
qDebug() << "[FetchTransactions] Time mismatch:" << dtStr
<< "expected" << searchMsk.toString("dd.MM.yyyy, HH:mm")
<< "expected" << searchLocal.toString("dd.MM.yyyy, HH:mm")
<< "diff=" << diffSecs << "sec";
AdbUtils::goBack(m_deviceId);
QThread::msleep(1000);
@ -434,7 +448,9 @@ void GetLastTransactionsScript::searchTransaction(const DeviceInfo &device, int
tx.amount = parsed.amount;
tx.bankName = m_appCode;
tx.bankTime = dtStr;
tx.bankTrExternalId = externalId;
tx.bankTrExternalId = externalId.isEmpty()
? QString("ozon_") + QString(dtStr).remove(',').trimmed().replace(' ', '_')
: externalId;
tx.status = TransactionStatus::Complete;
tx.timestamp = DateUtils::getUtcNow();
if (txTime.isValid()) tx.completeTime = txTime;
@ -474,7 +490,9 @@ void GetLastTransactionsScript::searchTransaction(const DeviceInfo &device, int
}
void GetLastTransactionsScript::scanRecentTransactions(const DeviceInfo &device, int accountId) {
const QTimeZone msk("Europe/Moscow");
const QString tzName = AdbUtils::getDeviceTimezone(m_deviceId);
QTimeZone deviceTz(tzName.toUtf8());
if (!deviceTz.isValid()) deviceTz = QTimeZone("Europe/Moscow");
const int navBarTop = device.screenHeight - (device.screenHeight / 10);
// Ждём появления транзакций
@ -581,7 +599,7 @@ void GetLastTransactionsScript::scanRecentTransactions(const DeviceInfo &device,
QDateTime parsed = QDateTime::fromString(dtStr, "dd.MM.yyyy, HH:mm");
if (!parsed.isValid()) parsed = QDateTime::fromString(dtStr, "dd.MM.yyyy,HH:mm");
if (parsed.isValid()) {
parsed.setTimeZone(msk);
parsed.setTimeZone(deviceTz);
tx.completeTime = parsed;
}
}

View File

@ -427,6 +427,10 @@ QString ScreenXmlParser::parseProfilePhone() {
QList<Node> ScreenXmlParser::findPinCodeNodes(const QString &pinCode) {
const QString cleanPin = QString(pinCode).remove(' ');
if (cleanPin.isEmpty()) {
qWarning() << "[findPinCodeNodes] pinCode is empty — check bank_profiles.pin_code in DB";
return {};
}
// Способ 1: ищем кликабельные ноды с текстом-цифрой (0-9)
// Поддерживаем форматы: "1", "key 1", и т.п.
QMap<QString, Node> digitMap;
@ -499,8 +503,11 @@ QList<Node> ScreenXmlParser::findPinCodeNodes(const QString &pinCode) {
// Сначала квадратные, потом любые
QList<Node> candidates = fuzzyGroup(true);
if (candidates.size() < 10)
qDebug() << "[findPinCodeNodes] fallback square candidates:" << candidates.size();
if (candidates.size() < 10) {
candidates = fuzzyGroup(false);
qDebug() << "[findPinCodeNodes] fallback any candidates:" << candidates.size();
}
if (candidates.size() < 10) {
// Дебаг: выводим все clickable ноды и их размеры
@ -575,8 +582,12 @@ QList<Node> ScreenXmlParser::findPinCodeNodes(const QString &pinCode) {
for (const QChar &c : cleanPin) {
if (const QString d = QString(c); digitMap.contains(d)) {
result.append(digitMap[d]);
} else {
qWarning() << "[findPinCodeNodes] fallback: digit not in digitMap:" << d;
}
}
qDebug() << "[findPinCodeNodes] fallback result size:" << result.size()
<< "digitMap size:" << digitMap.size();
return result;
}