1
0
forked from BRT/arc

Include androidId in TaskDumpMeta and update related scripts for improved device identification and tracking.

This commit is contained in:
slava 2026-05-31 21:23:50 +05:00
parent 35b2b3bcaa
commit 5c2aba0505
19 changed files with 102 additions and 40 deletions

View File

@ -37,7 +37,7 @@ void GetCardInfoScript::doStart() {
return;
}
const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone};
const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone, device.id};
TaskDumpRecorder::Scope dumpScope("black", "FETCH_CARDS", m_deviceId, dumpMeta, &m_error);
// Читаем текущий экран

View File

@ -35,7 +35,7 @@ void GetProfileInfoScript::doStart() {
return;
}
const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone};
const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone, device.id};
TaskDumpRecorder::Scope dumpScope("black", "FETCH_PROFILE_INFO", m_deviceId, dumpMeta, &m_error);
// Читаем текущий экран

View File

@ -36,7 +36,7 @@ void LoginAndCheckAccountsScript::doStart() {
return;
}
const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone};
const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone, device.id};
TaskDumpRecorder::Scope dumpScope("black", "FETCH_PROFILE", m_deviceId, dumpMeta, &m_error);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));

View File

@ -180,6 +180,8 @@ QString TaskDumpRecorder::buildCaption(const QString &errorMessage) const {
if (!desktopId.isEmpty())
body += QStringLiteral("Desktop: %1\n").arg(desktopId.toHtmlEscaped());
body += QStringLiteral("Device: %1").arg(m_adbDeviceIdRaw.toHtmlEscaped());
if (!m_meta.androidId.isEmpty())
body += QStringLiteral("\nAndroidId: %1").arg(m_meta.androidId.toHtmlEscaped());
if (!m_meta.bankProfileId.isEmpty())
body += QStringLiteral("\nProfile: %1").arg(m_meta.bankProfileId.toHtmlEscaped());
if (!m_meta.materialId.isEmpty())
@ -251,6 +253,7 @@ void TaskDumpRecorder::finishInternal(const QString &errorMessage) {
ts << "bank: " << m_bank << "\n"
<< "task: " << m_taskKind << "\n"
<< "device: " << m_adbDeviceIdRaw << "\n";
if (!m_meta.androidId.isEmpty()) ts << "android_id: " << m_meta.androidId << "\n";
if (!m_meta.bankProfileId.isEmpty()) ts << "profile_id: " << m_meta.bankProfileId << "\n";
if (!m_meta.profilePhone.isEmpty()) ts << "profile_phone: " << m_meta.profilePhone << "\n";
if (!m_meta.materialId.isEmpty()) ts << "material_id: " << m_meta.materialId << "\n";

View File

@ -13,6 +13,7 @@ struct TaskDumpMeta {
QString card; // пусто = нет
QString bankProfileId; // серверный UUID банковского профиля (детект в общих логах)
QString profilePhone; // телефон банковского профиля (не получателя)
QString androidId; // стабильный android_id устройства (в отличие от adbSerial не меняется)
};
class TaskDumpRecorder {

View File

@ -36,7 +36,7 @@ void GetCardInfoScript::doStart() {
return;
}
const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone};
const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone, device.id};
TaskDumpRecorder::Scope dumpScope("dushanbe", "FETCH_CARDS", m_deviceId, dumpMeta, &m_error);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));

View File

@ -18,6 +18,8 @@
#include <QCoreApplication>
#include <algorithm>
// Душанбе показывает суммы как "100,50", "100.50 TJS", "1 000,50",
// "1 000.50 TJS" и т.п. Голый QString::toDouble на таком вернёт 0,
// потому что требует, чтобы вся строка была валидным числом.
@ -276,7 +278,7 @@ void GetLastTransactionsScript::doStart() {
}
const double firstAmount = m_targets.isEmpty() ? 0.0 : m_targets.first().first;
const TaskDumpMeta dumpMeta{m_materialId, firstAmount, {}, {}, app.bankProfileId, app.phone};
const TaskDumpMeta dumpMeta{m_materialId, firstAmount, {}, {}, app.bankProfileId, app.phone, device.id};
TaskDumpRecorder::Scope dumpScope("dushanbe", "FETCH_TRANSACTIONS", m_deviceId, dumpMeta, &m_error);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
@ -386,16 +388,31 @@ void GetLastTransactionsScript::doStart() {
qWarning() << "[Dushanbe::GetLastTransactions] No account found";
}
// История отсортирована сверху-вниз: новые операции выше старых. Ищем цели
// в том же порядке (по времени, от новых к старым) — тогда один непрерывный
// проход вниз находит все, и возвращаться к началу между целями не нужно.
QList<QPair<double, QDateTime>> targets = m_targets;
std::sort(targets.begin(), targets.end(),
[](const QPair<double, QDateTime> &a, const QPair<double, QDateTime> &b) {
return a.second > b.second; // по убыванию времени (новые первыми)
});
// Ищем каждую цель из списка. Найденные отдаём сразу через transactionParsed;
// ненайденные копим и репортим одной агрегированной ошибкой в конце.
QStringList notFound;
for (int i = 0; i < m_targets.size(); ++i) {
for (int i = 0; i < targets.size(); ++i) {
if (QThread::currentThread()->isInterruptionRequested()) { m_error = "Interrupted"; break; }
const double amount = m_targets[i].first;
const QDateTime time = m_targets[i].second;
// Перед каждой целью (кроме первой) возвращаемся к началу Выписки.
if (i > 0) scrollHistoryToTop(device);
if (!searchOne(device, accountId, amount, time)) {
const double amount = targets[i].first;
const QDateTime time = targets[i].second;
// Проход идёт вниз от текущей позиции. Если цель не нашлась — возможно,
// предыдущая (более новая) цель отсутствовала и увела список вниз, а эта
// лежит выше. Тогда надёжно возвращаемся к началу Выписки и пробуем ещё раз.
bool found = searchOne(device, accountId, amount, time);
if (!found && m_error != "Interrupted") {
scrollHistoryToTop(device);
found = searchOne(device, accountId, amount, time);
}
if (!found) {
if (m_error == "Interrupted") break;
notFound << QString("amount=%1 time=%2")
.arg(QString::number(amount, 'f', 2), time.toString(Qt::ISODate));
@ -423,16 +440,48 @@ void GetLastTransactionsScript::doStart() {
}
void GetLastTransactionsScript::scrollHistoryToTop(const DeviceInfo &device) {
// Свайпы сверху-вниз поднимают список Выписки к началу.
for (int i = 0; i < 4; ++i) {
// Свайпы сверху-вниз поднимают список Выписки к началу. Поиск ОДНОЙ цели
// (searchOne) мог проскроллить вниз до maxScrolls(=10) раз, поэтому
// фиксированного числа свайпов вверх не хватает — крутим, пока верх списка
// не перестанет меняться (значит достигли начала), с запасом по итерациям.
static const QRegularExpression reTime(R"(\d{2}:\d{2}:\d{2})");
// Сигнатура верха списка: content-desc самой верхней View-строки операции
// (last line — время HH:MM:SS). Пустая строка — список не распознан/пуст.
auto topSignature = [&]() -> QString {
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
QString best;
int bestY = device.screenHeight + 1;
for (const Node &n : xmlScreenParser.findAllNodes()) {
if (n.className != "android.view.View") continue;
const QStringList lines = n.contentDesc.split('\n');
if (lines.size() < 5) continue;
if (!reTime.match(lines.last().trimmed()).hasMatch()) continue;
if (n.y1() < bestY) { bestY = n.y1(); best = n.contentDesc; }
}
return best;
};
const int x = device.screenWidth / 2;
const int fromY = device.screenHeight / 3; // ниже шапки — без pull-to-refresh
const int toY = device.screenHeight * 3 / 4; // длинный бросок вниз → контент вверх
const int maxUpSwipes = 14; // > maxScrolls(10) с запасом
QString prevTop = topSignature();
int stable = 0;
for (int i = 0; i < maxUpSwipes; ++i) {
if (QThread::currentThread()->isInterruptionRequested()) return;
AdbUtils::makeSwipe(m_deviceId, device.screenWidth / 2,
device.screenHeight / 3,
device.screenWidth / 2,
device.screenHeight * 2 / 3, 300);
QThread::msleep(500);
AdbUtils::makeSwipe(m_deviceId, x, fromY, x, toY, 500);
QThread::msleep(700);
const QString curTop = topSignature();
if (!curTop.isEmpty() && curTop == prevTop) {
if (++stable >= 2) break; // верх не меняется два свайпа подряд → начало списка
} else {
stable = 0;
prevTop = curTop;
}
}
QThread::msleep(800);
QThread::msleep(500);
}
bool GetLastTransactionsScript::searchOne(const DeviceInfo &device, int accountId,

View File

@ -39,7 +39,7 @@ void GetProfileInfoScript::doStart() {
return;
}
const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone};
const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone, device.id};
TaskDumpRecorder::Scope dumpScope("dushanbe", "FETCH_PROFILE_INFO", m_deviceId, dumpMeta, &m_error);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));

View File

@ -41,7 +41,7 @@ void LoginAndCheckAccountsScript::doStart() {
return;
}
const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone};
const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone, device.id};
TaskDumpRecorder::Scope dumpScope("dushanbe", "FETCH_PROFILE", m_deviceId, dumpMeta, &m_error);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));

View File

@ -123,7 +123,7 @@ void PayByCardScript::doStart() {
return;
}
const TaskDumpMeta dumpMeta{m_account.materialId, m_amount, {}, m_cardNumber, app.bankProfileId, app.phone};
const TaskDumpMeta dumpMeta{m_account.materialId, m_amount, {}, m_cardNumber, app.bankProfileId, app.phone, device.id};
TaskDumpRecorder::Scope dumpScope("dushanbe", "CREATE_TRANSACTION_CARD", device.adbSerial, dumpMeta, &m_error);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.adbSerial, ++m_counter));

View File

@ -120,7 +120,7 @@ void PayByPhoneScript::doStart() {
return;
}
const TaskDumpMeta dumpMeta{m_account.materialId, m_amount, m_phone, {}, app.bankProfileId, app.phone};
const TaskDumpMeta dumpMeta{m_account.materialId, m_amount, m_phone, {}, app.bankProfileId, app.phone, device.id};
TaskDumpRecorder::Scope dumpScope("dushanbe", "CREATE_TRANSACTION_PHONE", device.adbSerial, dumpMeta, &m_error);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.adbSerial, ++m_counter));

View File

@ -42,7 +42,7 @@ void GetCardInfoScript::doStart() {
return;
}
const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone};
const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone, device.id};
TaskDumpRecorder::Scope dumpScope("ozon", "FETCH_CARDS", m_deviceId, dumpMeta, &m_error);
// Читаем текущий экран

View File

@ -109,7 +109,7 @@ void GetLastTransactionsScript::doStart() {
}
const double firstAmount = m_targets.isEmpty() ? 0.0 : m_targets.first().first;
const TaskDumpMeta dumpMeta{m_materialId, firstAmount, {}, {}, app.bankProfileId, app.phone};
const TaskDumpMeta dumpMeta{m_materialId, firstAmount, {}, {}, app.bankProfileId, app.phone, device.id};
TaskDumpRecorder::Scope dumpScope("ozon", "FETCH_TRANSACTIONS", m_deviceId, dumpMeta, &m_error);
if (!navigateToTransactionList(device, app)) {

View File

@ -37,7 +37,7 @@ void GetProfileInfoScript::doStart() {
return;
}
const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone};
const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone, device.id};
TaskDumpRecorder::Scope dumpScope("ozon", "FETCH_PROFILE_INFO", m_deviceId, dumpMeta, &m_error);
// Читаем текущий экран

View File

@ -40,7 +40,7 @@ void LoginAndCheckAccountsScript::doStart() {
return;
}
const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone};
const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone, device.id};
TaskDumpRecorder::Scope dumpScope("ozon", "FETCH_PROFILE", m_deviceId, dumpMeta, &m_error);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));

View File

@ -91,7 +91,7 @@ void PayByCardScript::doStart() {
}
const QString &adbId = device.adbSerial;
const TaskDumpMeta dumpMeta{m_account.materialId, m_amount, {}, m_cardNumber, app.bankProfileId, app.phone};
const TaskDumpMeta dumpMeta{m_account.materialId, m_amount, {}, m_cardNumber, app.bankProfileId, app.phone, device.id};
TaskDumpRecorder::Scope dumpScope("ozon", "CREATE_TRANSACTION_CARD", adbId, dumpMeta, &m_error);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(adbId, ++m_counter));

View File

@ -100,7 +100,7 @@ void PayByPhoneScript::doStart() {
}
const QString &adbId = device.adbSerial;
const TaskDumpMeta dumpMeta{m_account.materialId, m_amount, m_phone, {}, app.bankProfileId, app.phone};
const TaskDumpMeta dumpMeta{m_account.materialId, m_amount, m_phone, {}, app.bankProfileId, app.phone, device.id};
TaskDumpRecorder::Scope dumpScope("ozon", "CREATE_TRANSACTION_PHONE", adbId, dumpMeta, &m_error);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(adbId, ++m_counter));

View File

@ -348,11 +348,12 @@ void EventHandler::updateEventThread(const QString &key, const EventInfo &event,
// ещё не отправлено: при частичном успехе (часть списка найдена и уже
// улетела на сервер) ретрай бессмыслен — переотправит дубли, поэтому
// ненайденные просто репортим ошибкой ниже.
if (!result.isEmpty() && event.type == EventType::FetchTransactions
&& m_parsedTxCount.value(key, 0) == 0
&& tryRetry(key, event, result)) {
return;
}
// Ретраи отключены: при ошибке сразу репортим как Error без повторов.
// if (!result.isEmpty() && event.type == EventType::FetchTransactions
// && m_parsedTxCount.value(key, 0) == 0
// && tryRetry(key, event, result)) {
// return;
// }
const EventStatus newStatus = result.isEmpty() ? EventStatus::Complete : EventStatus::Error;
@ -757,11 +758,12 @@ void EventHandler::startThreadForTask(const MaterialInfo &account, const EventIn
// Поиск транзакции: таймаут тоже ретраим (ещё до 2 раз), но только если
// ещё ни одной транзакции не отправлено (иначе ретрай переотправит дубли).
if (event.type == EventType::FetchTransactions
&& m_parsedTxCount.value(key, 0) == 0
&& tryRetry(key, event, "Script timeout (5 min)")) {
return;
}
// Ретраи отключены: при таймауте сразу репортим как Error без повторов.
// if (event.type == EventType::FetchTransactions
// && m_parsedTxCount.value(key, 0) == 0
// && tryRetry(key, event, "Script timeout (5 min)")) {
// return;
// }
// Помечаем событие как ошибку
EventDAO::updateEvent(event.id, EventStatus::Error, "Script timeout (5 min)");

View File

@ -40,7 +40,14 @@ DesktopSelectionWindow::DesktopSelectionWindow(const QJsonArray &desktops, QWidg
const QStringList devices = deviceNamesOf(d);
QString text = title;
if (!devices.isEmpty()) text += "\n" + devices.join("\n");
if (!devices.isEmpty()) {
// При длинном списке показываем только первые 3 устройства,
// а в следующей строке — сколько ещё осталось.
const int kMaxShown = 3;
text += "\n" + devices.mid(0, kMaxShown).join("\n");
if (devices.size() > kMaxShown)
text += QString("\n… ещё %1").arg(devices.size() - kMaxShown);
}
auto *btn = new QPushButton(text);
btn->setStyleSheet("text-align: left; padding: 8px;");