1
0
forked from BRT/arc
arc/services/scripting/ScriptBridges.cpp

1096 lines
50 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 "ScriptBridges.h"
#include <QBuffer>
#include <QByteArray>
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDateTime>
#include <QRandomGenerator>
#include <QSettings>
#include <QString>
#include <QThread>
#include <QRegularExpression>
#include <QJsonArray>
#include "adb/AdbUtils.h"
#include "black/ScreenXmlParser.h"
#include "ozon/ScreenXmlParser.h"
#include "android/xml/Node.h"
#include "db/BankProfileInfo.h"
#include "db/DeviceInfo.h"
#include "db/MaterialInfo.h"
#include "db/TransactionInfo.h"
#include "dao/BankProfileDAO.h"
#include "dao/DeviceDAO.h"
#include "dao/MaterialDAO.h"
#include "dao/SettingsDAO.h"
#include "dao/TransactionDAO.h"
#include "dump/TaskDumpRecorder.h"
#include "net/NetworkService.h"
#include "ocr/OcrUtils.h"
namespace Scripting {
QVariantMap nodeToVariantMap(const Node &node) {
Node copy = node; // isEmpty() не const, нельзя дернуть на const-ссылке
QVariantMap m;
m["x1"] = copy.x1();
m["y1"] = copy.y1();
m["x2"] = copy.x2();
m["y2"] = copy.y2();
m["x"] = copy.x();
m["y"] = copy.y();
m["width"] = copy.width();
m["height"] = copy.height();
m["text"] = copy.text;
m["contentDesc"] = copy.contentDesc;
m["resourceId"] = copy.resourceId;
m["className"] = copy.className;
m["hint"] = copy.hint;
m["clickable"] = copy.clickable;
m["focusable"] = copy.focusable;
m["password"] = copy.password;
m["isEmpty"] = copy.isEmpty();
return m;
}
// ─── LogBridge ──────────────────────────────────────────────────────────────
void LogBridge::debug(const QString &message) { qDebug().noquote() << "[js:" + m_tag + "]" << message; }
void LogBridge::info (const QString &message) { qInfo().noquote() << "[js:" + m_tag + "]" << message; }
void LogBridge::warn (const QString &message) { qWarning().noquote() << "[js:" + m_tag + "]" << message; }
void LogBridge::error(const QString &message) { qCritical().noquote() << "[js:" + m_tag + "]" << message; }
// ─── TimerBridge ────────────────────────────────────────────────────────────
void TimerBridge::sleep(int ms) {
if (ms <= 0) return;
// Дробим длинный sleep на 100мс шаги, чтобы можно было прервать поток.
const int step = 100;
while (ms > 0) {
if (QThread *t = QThread::currentThread(); t && t->isInterruptionRequested()) return;
const int chunk = ms > step ? step : ms;
QThread::msleep(static_cast<unsigned long>(chunk));
ms -= chunk;
}
}
bool TimerBridge::isInterrupted() const {
QThread *t = QThread::currentThread();
return t && t->isInterruptionRequested();
}
// ─── AdbBridge ──────────────────────────────────────────────────────────────
QString AdbBridge::getScreenDumpAsXml(const QString &deviceId, int idx) {
return AdbUtils::getScreenDumpAsXml(deviceId, idx);
}
QString AdbBridge::getRawScreenDumpAsXml(const QString &deviceId) {
return AdbUtils::getRawScreenDumpAsXml(deviceId);
}
bool AdbBridge::makeTap(const QString &deviceId, int x, int y) {
return AdbUtils::makeTap(deviceId, x, y);
}
bool AdbBridge::makeSwipe(const QString &deviceId, int x1, int y1, int x2, int y2) {
return AdbUtils::makeSwipe(deviceId, x1, y1, x2, y2);
}
bool AdbBridge::makeSwipeWithDuration(const QString &deviceId, int x1, int y1, int x2, int y2, int durationMs) {
return AdbUtils::makeSwipe(deviceId, x1, y1, x2, y2, durationMs);
}
bool AdbBridge::tryToStartApplication(const QString &deviceId, const QString &packageName) {
return AdbUtils::tryToStartApplication(deviceId, packageName);
}
bool AdbBridge::tryToKillApplication(const QString &deviceId, const QString &packageName) {
return AdbUtils::tryToKillApplication(deviceId, packageName);
}
QString AdbBridge::tryToGetRunningAppPid(const QString &deviceId, const QString &packageName) {
return AdbUtils::tryToGetRunningAppPid(deviceId, packageName);
}
bool AdbBridge::takeScreenshot(const QString &deviceId, const QString &name) {
return AdbUtils::takeScreenshot(deviceId, name);
}
bool AdbBridge::inputText(const QString &deviceId, const QString &text) {
return AdbUtils::inputText(deviceId, text);
}
bool AdbBridge::inputAdbIMEText(const QString &deviceId, const QString &text) {
return AdbUtils::inputAdbIMEText(deviceId, text);
}
bool AdbBridge::enableAdbIMEKeyboard(const QString &deviceId) {
return AdbUtils::enableAdbIMEKeyboard(deviceId);
}
bool AdbBridge::disableAdbIMEKeyboard(const QString &deviceId) {
return AdbUtils::disableAdbIMEKeyboard(deviceId);
}
bool AdbBridge::hideKeyboard(const QString &deviceId) {
return AdbUtils::hideKeyboard(deviceId);
}
bool AdbBridge::goBack(const QString &deviceId) {
return AdbUtils::goBack(deviceId);
}
void AdbBridge::unlockScreen(const QString &deviceId, int screenWidth, int screenHeight) {
AdbUtils::unlockScreen(deviceId, screenWidth, screenHeight);
}
QString AdbBridge::getDeviceTimezone(const QString &deviceId) {
return AdbUtils::getDeviceTimezone(deviceId);
}
QString AdbBridge::captureScreenshotBase64(const QString &deviceId) {
const QByteArray bytes = AdbUtils::captureScreenshotBytes(deviceId);
return QString::fromLatin1(bytes.toBase64());
}
// ─── DaoBridge ──────────────────────────────────────────────────────────────
static QVariantMap deviceToMap(const DeviceInfo &d) {
QVariantMap m;
if (d.id.isEmpty()) return m; // пустая мапа = null
m["id"] = d.id;
m["adbSerial"] = d.adbSerial;
m["name"] = d.name;
m["screenWidth"] = d.screenWidth;
m["screenHeight"] = d.screenHeight;
m["apiId"] = d.apiId;
return m;
}
static QVariantMap bankProfileToMap(const BankProfileInfo &p) {
QVariantMap m;
if (p.id == -1) return m;
m["id"] = p.id;
m["appCode"] = p.code; // BankProfileInfo.code = код банка в config.ini ("black", "ozon"...)
m["name"] = p.name;
m["package"] = p.package;
m["pinCode"] = p.pinCode;
m["status"] = p.status;
m["amount"] = p.amount;
m["fullName"] = p.fullName;
m["phone"] = p.phone;
m["email"] = p.email;
m["currency"] = p.currency;
m["bankProfileId"] = p.bankProfileId;
return m;
}
static QVariantMap materialToMap(const MaterialInfo &a) {
QVariantMap m;
if (a.id == -1) return m;
m["id"] = a.id;
m["materialId"] = a.materialId;
m["appCode"] = a.appCode;
m["deviceId"] = a.deviceId;
m["lastNumbers"] = a.lastNumbers;
m["cardNumber"] = a.cardNumber;
m["accountNumber"] = a.accountNumber;
m["amount"] = a.amount;
m["currency"] = a.currency;
m["description"] = a.description;
m["status"] = materialStatusToString(a.status);
return m;
}
static MaterialInfo materialFromMap(const QVariantMap &m) {
MaterialInfo a;
a.id = m.value("id", -1).toInt();
a.materialId = m.value("materialId").toString();
a.appCode = m.value("appCode").toString();
a.deviceId = m.value("deviceId").toString();
a.lastNumbers = m.value("lastNumbers").toString();
a.cardNumber = m.value("cardNumber").toString();
a.accountNumber = m.value("accountNumber").toString();
a.amount = m.value("amount").toDouble();
a.currency = m.value("currency").toString();
a.description = m.value("description").toString();
const QString status = m.value("status").toString();
if (!status.isEmpty()) a.status = materialStatusFromString(status);
a.updateTime = QDateTime::currentDateTimeUtc();
return a;
}
QVariantMap DaoBridge::deviceGetById(const QString &id) {
return deviceToMap(DeviceDAO::getDeviceById(id));
}
QVariantMap DaoBridge::deviceFindByAdbSerial(const QString &serial) {
return deviceToMap(DeviceDAO::findByAdbSerial(serial));
}
QVariantMap DaoBridge::bankProfileGet(const QString &deviceId, const QString &appCode) {
return bankProfileToMap(BankProfileDAO::getApplication(deviceId, appCode));
}
bool DaoBridge::bankProfileUpdateAmount(int profileId, double amount) {
return BankProfileDAO::updateAmount(profileId, amount);
}
bool DaoBridge::bankProfileUpdateProfileInfo(int profileId, const QString &fullName,
const QString &phone, const QString &email) {
return BankProfileDAO::updateProfileInfo(profileId, fullName, phone, email);
}
bool DaoBridge::bankProfileUpdateComment(int profileId, const QString &comment) {
return BankProfileDAO::updateComment(profileId, comment);
}
QVariantList DaoBridge::materialGetAccounts(const QString &deviceId, const QString &appCode) {
QVariantList list;
for (const MaterialInfo &acc : MaterialDAO::getAccounts(deviceId, appCode)) {
list.append(materialToMap(acc));
}
return list;
}
QVariantMap DaoBridge::materialFindAppAccount(const QString &deviceId, const QString &appCode,
const QString &lastNumbers) {
return materialToMap(MaterialDAO::findAppAccount(deviceId, appCode, lastNumbers));
}
bool DaoBridge::materialUpsert(const QVariantMap &material) {
return MaterialDAO::upsertAccount(materialFromMap(material));
}
bool DaoBridge::materialUpdateCardNumber(int accountId, const QString &cardNumber) {
return MaterialDAO::updateCardNumber(accountId, cardNumber);
}
bool DaoBridge::materialUpdateAccountById(int id, const QString &status,
const QString &description, const QString &currency) {
return MaterialDAO::updateAccountById(id, status, description, currency);
}
QString DaoBridge::settingsGet(const QString &key) {
return SettingsDAO::get(key);
}
namespace {
TransactionInfo txFromMap(const QVariantMap &m) {
TransactionInfo tx;
tx.id = m.value("id", -1).toInt();
tx.accountId = m.value("accountId", -1).toInt();
tx.amount = m.value("amount").toDouble();
tx.fee = m.value("fee").toDouble();
tx.phone = m.value("phone").toString();
tx.name = m.value("name").toString();
tx.description = m.value("description").toString();
tx.updatedDescription = m.value("updatedDescription").toString();
tx.bankName = m.value("bankName").toString();
tx.bankTime = m.value("bankTime").toString();
tx.bankTrExternalId = m.value("bankTrExternalId").toString();
const QString typeS = m.value("type").toString();
if (!typeS.isEmpty()) tx.type = transactionTypeFromString(typeS);
const QString statusS = m.value("status").toString();
if (!statusS.isEmpty()) tx.status = transactionStatusFromString(statusS);
auto parseDt = [](const QVariant &v) -> QDateTime {
if (!v.isValid() || v.isNull()) return {};
if (v.canConvert<QDateTime>()) return v.toDateTime();
return QDateTime::fromString(v.toString(), Qt::ISODate);
};
tx.timestamp = parseDt(m.value("timestamp"));
tx.completeTime = parseDt(m.value("completeTime"));
tx.updateTime = parseDt(m.value("updateTime"));
if (!tx.timestamp.isValid()) tx.timestamp = QDateTime::currentDateTimeUtc();
return tx;
}
} // namespace
int DaoBridge::transactionInsert(const QVariantMap &tx) {
return TransactionDAO::insertTransaction(txFromMap(tx));
}
bool DaoBridge::transactionUpdateBankTrExternalId(int id, const QString &externalId) {
return TransactionDAO::updateBankTrExternalId(id, externalId);
}
// ─── DumpBridge ─────────────────────────────────────────────────────────────
void DumpBridge::beginScope(const QString &bank, const QString &taskKind, const QString &adbDeviceId,
const QVariantMap &meta) {
if (m_scope) {
qWarning() << "[Scripting] DumpBridge: beginScope called twice, ignoring";
return;
}
TaskDumpMeta tm;
tm.materialId = meta.value("materialId").toString();
tm.amount = meta.value("amount").toDouble();
tm.phone = meta.value("phone").toString();
tm.card = meta.value("card").toString();
m_errorPtr = new QString();
m_scope = new TaskDumpRecorder::Scope(bank, taskKind, adbDeviceId, tm, m_errorPtr);
}
void DumpBridge::endScope(const QString &errorMessage) {
if (!m_scope) return;
if (m_errorPtr) *m_errorPtr = errorMessage;
delete static_cast<TaskDumpRecorder::Scope *>(m_scope);
m_scope = nullptr;
delete m_errorPtr;
m_errorPtr = nullptr;
}
// ─── ScriptObjectBridge ────────────────────────────────────────────────────
void ScriptObjectBridge::emitTransactionParsed(const QString &jsonStr) {
if (!m_sink) {
qDebug() << "[Scripting] emitTransactionParsed called but no signalSink — dropping";
return;
}
QJsonParseError err;
const QJsonDocument doc = QJsonDocument::fromJson(jsonStr.toUtf8(), &err);
if (err.error != QJsonParseError::NoError || !doc.isObject()) {
qWarning() << "[Scripting] emitTransactionParsed: bad JSON" << err.errorString();
return;
}
// QMetaObject::invokeMethod умеет вызывать Qt-сигналы. Для thread-safety
// используем QueuedConnection — JS крутится в worker-потоке, EventHandler
// тоже там же, но queued тут не помешает.
QMetaObject::invokeMethod(m_sink, "transactionParsed", Qt::DirectConnection,
Q_ARG(QJsonObject, doc.object()));
}
// ─── OcrBridge ─────────────────────────────────────────────────────────────
QString OcrBridge::recognizeFromScreen(const QString &deviceId) {
const QByteArray png = AdbUtils::captureScreenshotBytes(deviceId);
if (png.isEmpty()) return {};
return OcrUtils::recognizeText(png);
}
QString OcrBridge::extractTransactionId(const QString &ocrText) {
return OcrUtils::extractTransactionId(ocrText);
}
// ─── BlackParserBridge ──────────────────────────────────────────────────────
BlackParserBridge::BlackParserBridge(QObject *parent)
: QObject(parent), m_parser(new Black::ScreenXmlParser(this)) {}
BlackParserBridge::~BlackParserBridge() = default;
void BlackParserBridge::parseAndSaveXml(const QString &xml) { m_parser->parseAndSaveXml(xml); }
bool BlackParserBridge::isHomeScreen() { return m_parser->isHomeScreen(); }
bool BlackParserBridge::isSideMenu() { return m_parser->isSideMenu(); }
bool BlackParserBridge::isPinCodeScreen() { return m_parser->isPinCodeScreen(); }
bool BlackParserBridge::isProfilePage() { return m_parser->isProfilePage(); }
bool BlackParserBridge::isCVUScreen() { return m_parser->isCVUScreen(); }
bool BlackParserBridge::isTransactionLoadingScreen() { return m_parser->isTransactionLoadingScreen(); }
bool BlackParserBridge::isTransactionListScreen() { return m_parser->isTransactionListScreen(); }
bool BlackParserBridge::isPayInputCardNumberScreen() { return m_parser->isPayInputCardNumberScreen(); }
bool BlackParserBridge::isPayTransferConfirmScreen() { return m_parser->isPayTransferConfirmScreen(); }
bool BlackParserBridge::isPayInputSumScreen() { return m_parser->isPayInputSumScreen(); }
bool BlackParserBridge::isPayConfirmScreen() { return m_parser->isPayConfirmScreen(); }
bool BlackParserBridge::isPaySuccessScreen() { return m_parser->isPaySuccessScreen(); }
QVariantMap BlackParserBridge::findHolaButton() { return nodeToVariantMap(m_parser->findHolaButton()); }
QVariantMap BlackParserBridge::findTextNode(const QString &text) { return nodeToVariantMap(m_parser->findTextNode(text)); }
QVariantMap BlackParserBridge::findNodeByContentDesc(const QString &desc) { return nodeToVariantMap(m_parser->findNodeByContentDesc(desc)); }
QVariantMap BlackParserBridge::findNodeByContentDescContains(const QString &desc) { return nodeToVariantMap(m_parser->findNodeByContentDesc(desc, true)); }
QVariantMap BlackParserBridge::findButtonNode(const QString &text, bool contains) { return nodeToVariantMap(m_parser->findButtonNode(text, contains)); }
QVariantMap BlackParserBridge::findFirstEditText() { return nodeToVariantMap(m_parser->findFirstEditText()); }
QVariantMap BlackParserBridge::findPinCodeEditText() { return nodeToVariantMap(m_parser->findPinCodeEditText()); }
QVariantMap BlackParserBridge::findVerMasAfterTuActividad() { return nodeToVariantMap(m_parser->findVerMasAfterTuActividad()); }
QVariantMap BlackParserBridge::findEditTextByHint(const QString &hint) { return nodeToVariantMap(m_parser->findEditTextByHint(hint)); }
QString BlackParserBridge::parseUserNameFromHomeScreen() { return m_parser->parseUserNameFromHomeScreen(); }
double BlackParserBridge::parseBalanceFromHomeScreen() { return m_parser->parseBalanceFromHomeScreen(); }
QString BlackParserBridge::parseProfileNombre() { return m_parser->parseProfileNombre(); }
QString BlackParserBridge::parseProfileApellido() { return m_parser->parseProfileApellido(); }
QString BlackParserBridge::parseProfileEmail() { return m_parser->parseProfileEmail(); }
QString BlackParserBridge::parseProfilePhone() { return m_parser->parseProfilePhone(); }
QString BlackParserBridge::parseCVUNumber() { return m_parser->parseCVUNumber(); }
QString BlackParserBridge::parseRecipientNameFromConfirm(){ return m_parser->parseRecipientNameFromConfirm(); }
QString BlackParserBridge::parsePaySuccessDateTime() { return m_parser->parsePaySuccessDateTime(); }
QString BlackParserBridge::parsePaySuccessCoelsaId() { return m_parser->parsePaySuccessCoelsaId(); }
// ─── BlackHelpersBridge ─────────────────────────────────────────────────────
// Локальная копия логики Black::CommonScript::runAppAndGoToHomeScreen — нужна
// чтобы JS мог вызвать готовый высокоуровневый flow, не переписывая login заново.
bool BlackHelpersBridge::runAppAndGoToHomeScreen(const QString &deviceId, const QString &packageName,
const QString &pinCode, int width, int height) {
Black::ScreenXmlParser *parser = m_parserBridge->parser();
AdbUtils::unlockScreen(deviceId, width, height);
if (const QString pid = AdbUtils::tryToGetRunningAppPid(deviceId, packageName); !pid.isEmpty()) {
qDebug() << "[js:black/helpers] Process already running, pid:" << pid;
AdbUtils::getScreenDumpAsXml(deviceId, 1000);
AdbUtils::tryToKillApplication(deviceId, packageName);
}
if (!AdbUtils::tryToStartApplication(deviceId, packageName)) {
AdbUtils::takeScreenshot(deviceId, "run_app_error");
qWarning() << "[js:black/helpers] Process has not started";
return false;
}
QThread::msleep(4500);
int counter = 0;
bool pinInjected = false;
int xmlCounter = 0;
while (true) {
if (QThread *t = QThread::currentThread(); t && t->isInterruptionRequested()) return false;
counter++;
if (counter > 10) {
AdbUtils::takeScreenshot(deviceId, "too_many_attempts");
return false;
}
parser->parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++xmlCounter));
if (parser->isHomeScreen()) return true;
if (!pinInjected && parser->isPinCodeScreen()) {
// Inline-копия inputPinCode (тоже из CommonScript)
Node editText = parser->findPinCodeEditText();
if (!editText.isEmpty()) {
AdbUtils::makeTap(deviceId, editText.x(), editText.y());
QThread::msleep(500);
AdbUtils::inputText(deviceId, pinCode);
QThread::msleep(500);
} else {
qWarning() << "[js:black/helpers] PIN EditText not found";
}
pinInjected = true;
QThread::msleep(3000);
continue;
}
QThread::msleep(1000);
}
}
void BlackHelpersBridge::postAccountsData(const QVariantList &accounts) {
if (accounts.isEmpty()) return;
auto *thread = new QThread;
auto *worker = new NetworkService;
QJsonArray list;
QString deviceId;
for (const QVariant &v : accounts) {
const QVariantMap acc = v.toMap();
QJsonObject obj;
obj["appName"] = acc.value("appCode").toString();
obj["lastNumbers"] = acc.value("lastNumbers").toString();
obj["amount"] = acc.value("amount").toDouble();
obj["description"] = acc.value("description").toString();
obj["currency"] = acc.value("currency").toString();
obj["status"] = acc.value("status").toString();
list.append(obj);
if (deviceId.isEmpty()) deviceId = acc.value("deviceId").toString();
}
worker->setDeviceId(deviceId);
worker->setPayload(QJsonDocument(list).toJson());
worker->moveToThread(thread);
QObject::connect(thread, &QThread::started, worker, &NetworkService::postAccountsData);
QObject::connect(worker, &NetworkService::finished, thread, &QThread::quit);
QObject::connect(worker, &NetworkService::finished, worker, &QObject::deleteLater);
QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->start();
}
void BlackHelpersBridge::createBankProfileIfNeeded(const QString &deviceId, const QString &appCode, double parsedBalance) {
const BankProfileInfo app = BankProfileDAO::getApplication(deviceId, appCode);
if (app.fullName.trimmed().isEmpty() || app.phone.trimmed().isEmpty()) {
qWarning() << "[js:black/helpers] createBankProfileIfNeeded: skipping — fullName or phone is empty";
return;
}
const QMap<QString, int> currencyCodes = {
{"RUB", 643}, {"USD", 840}, {"KRW", 410}, {"AZN", 944}, {"ARS", 32}, {"TJS", 972}
};
const int currencyCode = currencyCodes.value(app.currency.toUpper(), 840);
const QStringList parts = app.fullName.split(' ', Qt::SkipEmptyParts);
const QSettings settings("config.ini", QSettings::IniFormat);
const QString phoneCode = settings.value(appCode + "/phone_code").toString();
QString phone = app.phone;
phone.remove(QRegularExpression("[^0-9]"));
if (!phoneCode.isEmpty() && !phone.startsWith(phoneCode)) {
if (phone.startsWith('8') && phoneCode != "8") {
phone.replace(0, 1, phoneCode);
} else {
phone.prepend(phoneCode);
}
}
phone.prepend('+');
const DeviceInfo device = DeviceDAO::getDeviceById(deviceId);
QJsonObject profileBody;
profileBody["first_name"] = parts.value(0);
profileBody["middle_name"] = parts.size() >= 3 ? parts.value(1) : QString("");
profileBody["last_name"] = parts.size() >= 3 ? parts.value(2) : parts.value(1);
profileBody["bank_name"] = appCode;
profileBody["phone_number"] = phone;
const double balance = (parsedBalance >= 0.0) ? parsedBalance : app.amount;
profileBody["balance"] = QString::number(qRound64(balance * 100));
profileBody["state"] = (app.status == "active") ? "enabled" : "disabled";
profileBody["device_id"] = device.apiId;
profileBody["currency"] = currencyCode;
NetworkService ns;
ns.setDeviceId(deviceId);
ns.setPayload(QJsonDocument(profileBody).toJson());
if (app.bankProfileId.isEmpty()) {
ns.addExtra("app_id", app.id);
ns.postBankProfile();
} else {
ns.addExtra("bank_profile_id", app.bankProfileId);
ns.patchBankProfile();
}
}
// ─── OzonParserBridge ──────────────────────────────────────────────────────
OzonParserBridge::OzonParserBridge(QObject *parent)
: QObject(parent), m_parser(new Ozon::ScreenXmlParser(this)) {}
OzonParserBridge::~OzonParserBridge() = default;
void OzonParserBridge::parseAndSaveXml(const QString &xml) { m_parser->parseAndSaveXml(xml); }
bool OzonParserBridge::isHomeScreen() { return m_parser->isHomeScreen(); }
bool OzonParserBridge::isProfileScreen() { return m_parser->isProfileScreen(); }
QVariantMap OzonParserBridge::tryToFindAdBanner() { return nodeToVariantMap(m_parser->tryToFindAdBanner()); }
QVariantMap OzonParserBridge::findUserNameOnHomeScreen(int screenHeight){ return nodeToVariantMap(m_parser->findUserNameOnHomeScreen(screenHeight)); }
QVariantMap OzonParserBridge::findCardButtonByLastNumbers(const QString &ln) { return nodeToVariantMap(m_parser->findCardButtonByLastNumbers(ln)); }
QVariantMap OzonParserBridge::findButtonNode(const QString &text, bool contains) { return nodeToVariantMap(m_parser->findButtonNode(text, contains)); }
QVariantMap OzonParserBridge::findTextNode(const QString &text) { return nodeToVariantMap(m_parser->findTextNode(text)); }
QVariantMap OzonParserBridge::tryToFindGooglePlayBanner(int w, int h) { return nodeToVariantMap(m_parser->tryToFindGooglePlayBanner(w, h)); }
QVariantMap OzonParserBridge::findFirstEditText() { return nodeToVariantMap(m_parser->findFirstEditText()); }
QVariantMap OzonParserBridge::findEditTextByHint(const QString &h) { return nodeToVariantMap(m_parser->findEditTextByHint(h)); }
QVariantMap OzonParserBridge::findPanCardNumberInput() { return nodeToVariantMap(m_parser->findPanCardNumberInput()); }
QVariantMap OzonParserBridge::findMainAccountButton() { return nodeToVariantMap(m_parser->findMainAccountButton()); }
QVariantMap OzonParserBridge::findNodeByContentDesc(const QString &cd) { return nodeToVariantMap(m_parser->findNodeByContentDesc(cd)); }
QVariantList OzonParserBridge::findAllNodes() {
QVariantList list;
for (const Node &n : m_parser->findAllNodes()) list.append(nodeToVariantMap(n));
return list;
}
QVariantList OzonParserBridge::findAllButtons() {
QVariantList list;
for (const Node &n : m_parser->findAllButtons()) list.append(nodeToVariantMap(n));
return list;
}
QVariantList OzonParserBridge::tryToFindPinCode(const QString &pin, const QString &title) {
QVariantList list;
for (const Node &n : m_parser->tryToFindPinCode(pin, title)) list.append(nodeToVariantMap(n));
return list;
}
namespace {
// Конверсия TransactionInfo → JS-объект. Используется парсер-методами,
// возвращающими спарсенные транзакции из XML.
QVariantMap txInfoToMap(const TransactionInfo &tx) {
QVariantMap m;
m["id"] = tx.id;
m["accountId"] = tx.accountId;
m["amount"] = tx.amount;
m["fee"] = tx.fee;
m["status"] = transactionStatusToString(tx.status);
m["type"] = transactionTypeToString(tx.type);
m["phone"] = tx.phone;
m["name"] = tx.name;
m["description"] = tx.description;
m["updatedDescription"] = tx.updatedDescription;
m["bankName"] = tx.bankName;
m["bankTime"] = tx.bankTime;
m["bankTrExternalId"] = tx.bankTrExternalId;
if (tx.timestamp.isValid()) m["timestamp"] = tx.timestamp.toString(Qt::ISODateWithMs);
if (tx.completeTime.isValid()) m["completeTime"] = tx.completeTime.toString(Qt::ISODateWithMs);
if (tx.updateTime.isValid()) m["updateTime"] = tx.updateTime.toString(Qt::ISODateWithMs);
return m;
}
} // namespace
bool OzonParserBridge::isTransactionListScreen() { return m_parser->isTransactionListScreen(); }
bool OzonParserBridge::hasCollapsedTransactionsBelow(int sh) { return m_parser->hasCollapsedTransactionsBelow(sh); }
QVariantList OzonParserBridge::findTransactionButtons(int maxCount) {
QVariantList list;
for (const Node &n : m_parser->findTransactionButtons(maxCount)) list.append(nodeToVariantMap(n));
return list;
}
QVariantList OzonParserBridge::getLast2DaysTransactions() {
QVariantList list;
for (const Node &n : m_parser->getLast2DaysTransactions()) list.append(nodeToVariantMap(n));
return list;
}
QVariantList OzonParserBridge::parseTodayAndYesterdayHistory() {
QVariantList list;
for (const TransactionInfo &t : m_parser->parseTodayAndYesterdayHistory()) list.append(txInfoToMap(t));
return list;
}
QVariantList OzonParserBridge::parseTodayAndYesterdayReportHistory() {
QVariantList list;
for (const TransactionInfo &t : m_parser->parseTodayAndYesterdayReportHistory()) list.append(txInfoToMap(t));
return list;
}
QVariantMap OzonParserBridge::parseTransactionInfo(int w, int h) { return txInfoToMap(m_parser->parseTransactionInfo(w, h)); }
QVariantMap OzonParserBridge::parseTransactionInfoHistory(int w, int h) { return txInfoToMap(m_parser->parseTransactionInfoHistory(w, h)); }
QVariantMap OzonParserBridge::parseTransactionDetail() { return txInfoToMap(m_parser->parseTransactionDetail()); }
QVariantMap OzonParserBridge::parseTransactionButtonText(const QString &text) { return txInfoToMap(m_parser->parseTransactionButtonText(text)); }
QVariantList OzonParserBridge::parseCardsOnHomeScreen() {
QVariantList list;
for (const MaterialInfo &c : m_parser->parseCardsOnHomeScreen()) {
QVariantMap m;
m["lastNumbers"] = c.lastNumbers;
m["description"] = c.description;
m["amount"] = c.amount;
m["currency"] = c.currency;
list.append(m);
}
return list;
}
QString OzonParserBridge::parseProfileFullName() { return m_parser->parseProfileFullName(); }
QString OzonParserBridge::parseProfilePhone() { return m_parser->parseProfilePhone(); }
QString OzonParserBridge::findFullCardNumber() { return m_parser->findFullCardNumber(); }
QVariantList OzonParserBridge::parseAccountsInfo() {
QVariantList list;
for (const auto &pair : m_parser->parseAccountsInfo()) {
QVariantMap m;
m["lastNumbers"] = pair.first.lastNumbers;
m["description"] = pair.first.description;
m["amount"] = pair.first.amount;
m["currency"] = pair.first.currency;
QVariantMap entry;
entry["material"] = m;
entry["node"] = nodeToVariantMap(pair.second);
list.append(entry);
}
return list;
}
// ─── OzonHelpersBridge ─────────────────────────────────────────────────────
namespace {
// Локальная копия Ozon::CommonScript::inputPinCode — нужна целиком в helper'е,
// чтобы не тянуть CommonScript-инстанс ради одной static-функции.
bool ozonInputPinCode(const QString &deviceId, const QList<Node> &pincode) {
for (const Node &node : pincode) {
if (!AdbUtils::makeTap(deviceId, node.x(), node.y())) {
AdbUtils::makeTap(deviceId, node.x(), node.y());
}
QThread::msleep(QRandomGenerator::global()->bounded(100, 901));
}
return true;
}
bool interrupted() {
QThread *t = QThread::currentThread();
return t && t->isInterruptionRequested();
}
} // namespace
// Полная копия Ozon::CommonScript::runAppAndGoToHomeScreen — fast path + цикл с
// обработкой баннеров и пин-кода. Использует ТОТ ЖЕ парсер, что и JS-скрипт.
bool OzonHelpersBridge::runAppAndGoToHomeScreen(const QString &deviceId, const QString &packageName,
const QString &pinCode, int width, int height) {
Ozon::ScreenXmlParser *parser = m_parserBridge->parser();
int xmlCounter = 0;
AdbUtils::unlockScreen(deviceId, width, height);
// Fast path: если приложение уже запущено
if (const QString pid = AdbUtils::tryToGetRunningAppPid(deviceId, packageName); !pid.isEmpty()) {
qDebug() << "[js:ozon/helpers] Process already running, pid:" << pid;
parser->parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++xmlCounter));
if (parser->isHomeScreen()) {
qDebug() << "[js:ozon/helpers] Fast path: already on Home screen";
return true;
}
const QList<Node> fastPincode = parser->tryToFindPinCode(pinCode, QStringLiteral("Введите ПИН"));
if (fastPincode.size() == pinCode.length() && fastPincode.size() >= 4) {
qDebug() << "[js:ozon/helpers] Fast path: PIN screen, entering PIN";
if (Node wake = parser->findTextNode(QStringLiteral("Введите ПИН")); !wake.isEmpty()) {
AdbUtils::makeTap(deviceId, wake.x(), wake.y());
}
ozonInputPinCode(deviceId, fastPincode);
QThread::msleep(3000);
for (int i = 0; i < 10; ++i) {
if (interrupted()) return false;
parser->parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++xmlCounter));
if (parser->isHomeScreen()) {
qDebug() << "[js:ozon/helpers] Fast path: Home reached after PIN";
return true;
}
if (Node refreshBtn = parser->findNodeByContentDesc(QStringLiteral("Обновить"));
refreshBtn.x() > 0 && refreshBtn.y() > 0) {
qDebug() << "[js:ozon/helpers] Fast path: page error after PIN, tapping 'Обновить'";
AdbUtils::makeTap(deviceId, refreshBtn.x(), refreshBtn.y());
QThread::msleep(5000);
}
QThread::msleep(1000);
}
}
qDebug() << "[js:ozon/helpers] Fast path failed, restarting app";
AdbUtils::tryToKillApplication(deviceId, packageName);
}
if (!AdbUtils::tryToStartApplication(deviceId, packageName)) {
AdbUtils::takeScreenshot(deviceId, "run_app_error");
qWarning() << "[js:ozon/helpers] Process has not started";
return false;
}
QThread::msleep(4500);
int counter = 0;
bool pinInjected = false;
while (true) {
if (interrupted()) return false;
if (++counter > 10) {
AdbUtils::takeScreenshot(deviceId, "too_many_attempts");
return false;
}
parser->parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++xmlCounter));
if (parser->isHomeScreen()) return true;
if (Node bannerOrDialog = parser->tryToFindBannerOrDialog(width, height);
bannerOrDialog.x() != -1 && bannerOrDialog.y() != -1) {
AdbUtils::makeTap(deviceId, bannerOrDialog.x(), bannerOrDialog.y());
QThread::msleep(1000);
continue;
}
if (Node refreshBtn = parser->findNodeByContentDesc(QStringLiteral("Обновить"));
refreshBtn.x() > 0 && refreshBtn.y() > 0) {
qDebug() << "[js:ozon/helpers] Page load error, tapping 'Обновить'";
AdbUtils::makeTap(deviceId, refreshBtn.x(), refreshBtn.y());
QThread::msleep(5000);
continue;
}
if (Node securityBanner = parser->tryToFindSecurityBanner(); !securityBanner.isEmpty()) {
AdbUtils::makeTap(deviceId, securityBanner.x(), securityBanner.y());
QThread::msleep(1000);
continue;
}
if (Node gpNode = parser->tryToFindGooglePlayBanner(width, height); !gpNode.isEmpty()) {
qDebug() << "[js:ozon/helpers] Google Play update banner detected, closing...";
AdbUtils::makeTap(deviceId, gpNode.x(), gpNode.y());
QThread::msleep(1500);
continue;
}
if (Node adNode = parser->tryToFindAdBanner(); !adNode.isEmpty()) {
qDebug() << "[js:ozon/helpers] Ad bottom sheet detected, closing...";
AdbUtils::makeTap(deviceId, adNode.x(), adNode.y());
QThread::msleep(1000);
parser->parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++xmlCounter));
if (Node stillAd = parser->tryToFindAdBanner(); !stillAd.isEmpty()) {
qDebug() << "[js:ozon/helpers] Ad still visible, pressing back...";
AdbUtils::goBack(deviceId);
QThread::msleep(1000);
}
continue;
}
if (!pinInjected) {
const QList<Node> pincode = parser->tryToFindPinCode(pinCode, QStringLiteral("Введите ПИН"));
if (pincode.size() == pinCode.length() && pincode.size() >= 4) {
if (Node wake = parser->findTextNode(QStringLiteral("Введите ПИН")); !wake.isEmpty()) {
AdbUtils::makeTap(deviceId, wake.x(), wake.y());
}
ozonInputPinCode(deviceId, pincode);
pinInjected = true;
QThread::msleep(3000);
continue;
}
}
QThread::msleep(1000);
}
}
bool OzonHelpersBridge::tryToCloseAllBannersOnHomePage(const QString &deviceId, int width, int height) {
Ozon::ScreenXmlParser *parser = m_parserBridge->parser();
int xmlCounter = 0;
bool closedAny = false;
for (int i = 0; i < 5; ++i) {
if (interrupted()) return closedAny;
bool closedThis = false;
if (Node gpNode = parser->tryToFindGooglePlayBanner(width, height); !gpNode.isEmpty()) {
qDebug() << "[js:ozon/helpers] GP banner detected, closing";
AdbUtils::makeTap(deviceId, gpNode.x(), gpNode.y());
QThread::msleep(1500);
closedThis = true;
} else if (Node adNode = parser->tryToFindAdBanner(); !adNode.isEmpty()) {
qDebug() << "[js:ozon/helpers] Ad bottom sheet detected, closing";
AdbUtils::makeTap(deviceId, adNode.x(), adNode.y());
QThread::msleep(1000);
parser->parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++xmlCounter));
if (Node stillAd = parser->tryToFindAdBanner(); !stillAd.isEmpty()) {
AdbUtils::goBack(deviceId);
QThread::msleep(1000);
}
closedAny = true;
parser->parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++xmlCounter));
continue;
} else if (Node bod = parser->tryToFindBannerOrDialog(width, height);
bod.x() != -1 && bod.y() != -1) {
AdbUtils::makeTap(deviceId, bod.x(), bod.y());
QThread::msleep(500);
closedThis = true;
}
if (!closedThis) break;
closedAny = true;
parser->parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++xmlCounter));
}
return closedAny;
}
bool OzonHelpersBridge::closeGooglePlayBannerIfVisible(const QString &deviceId, int width, int height) {
Ozon::ScreenXmlParser *parser = m_parserBridge->parser();
int xmlCounter = 0;
if (Node denyBtn = parser->tryToFindSystemPermissionDenyButton(); !denyBtn.isEmpty()) {
qDebug() << "[js:ozon/helpers] System permission dialog detected, tapping 'ЗАПРЕТИТЬ'";
AdbUtils::makeTap(deviceId, denyBtn.x(), denyBtn.y());
QThread::msleep(1500);
parser->parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++xmlCounter));
return true;
}
Node gpNode = parser->tryToFindGooglePlayBanner(width, height);
if (gpNode.isEmpty()) return false;
qDebug() << "[js:ozon/helpers] GP banner detected mid-task, closing";
AdbUtils::makeTap(deviceId, gpNode.x(), gpNode.y());
QThread::msleep(1500);
parser->parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++xmlCounter));
return true;
}
void OzonHelpersBridge::createBankProfileIfNeeded(const QString &deviceId, const QString &appCode, double parsedBalance) {
const BankProfileInfo app = BankProfileDAO::getApplication(deviceId, appCode);
if (app.fullName.trimmed().isEmpty() || app.phone.trimmed().isEmpty()) {
qWarning() << "[js:ozon/helpers] createBankProfileIfNeeded: skipping — fullName or phone is empty";
return;
}
const QMap<QString, int> currencyCodes = {
{"RUB", 643}, {"USD", 840}, {"KRW", 410}, {"AZN", 944}, {"ARS", 32}, {"TJS", 972}
};
const int currencyCode = currencyCodes.value(app.currency.toUpper(), 643);
const QStringList parts = app.fullName.split(' ', Qt::SkipEmptyParts);
const QSettings settings("config.ini", QSettings::IniFormat);
const QString phoneCode = settings.value(appCode + "/phone_code").toString();
QString phone = app.phone;
phone.remove(QRegularExpression("[^0-9]"));
if (!phoneCode.isEmpty() && !phone.startsWith(phoneCode)) {
if (phone.startsWith('8') && phoneCode != "8") {
phone.replace(0, 1, phoneCode);
} else {
phone.prepend(phoneCode);
}
}
phone.prepend('+');
const DeviceInfo device = DeviceDAO::getDeviceById(deviceId);
QJsonObject profileBody;
profileBody["first_name"] = parts.value(0);
profileBody["middle_name"] = parts.size() >= 3 ? parts.value(1) : QString("");
profileBody["last_name"] = parts.size() >= 3 ? parts.value(2) : parts.value(1);
profileBody["bank_name"] = appCode;
profileBody["phone_number"] = phone;
const double balance = (parsedBalance >= 0.0) ? parsedBalance : app.amount;
profileBody["balance"] = QString::number(qRound64(balance * 100));
profileBody["state"] = (app.status == "active") ? "enabled" : "disabled";
profileBody["device_id"] = device.apiId;
profileBody["currency"] = currencyCode;
NetworkService ns;
ns.setDeviceId(deviceId);
ns.setPayload(QJsonDocument(profileBody).toJson());
if (app.bankProfileId.isEmpty()) {
ns.addExtra("app_id", app.id);
ns.postBankProfile();
} else {
ns.addExtra("bank_profile_id", app.bankProfileId);
ns.patchBankProfile();
}
}
// Порт Ozon::CommonScript::goToAllProducts — для Ozon "Финансы" tab.
bool OzonHelpersBridge::goToAllProducts(const QString &deviceId, int width, int height) {
Ozon::ScreenXmlParser *parser = m_parserBridge->parser();
int xmlCounter = 0;
parser->parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, xmlCounter));
if (tryToCloseAllBannersOnHomePage(deviceId, width, height)) {
QThread::msleep(500);
parser->parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++xmlCounter));
}
if (parser->isHomeScreen()) return true;
for (int i = 0; i < 3; ++i) {
if (interrupted()) return false;
if (Node financeTab = parser->findNodeByContentDesc(QStringLiteral("Финансы"));
!financeTab.isEmpty()) {
AdbUtils::makeTap(deviceId, financeTab.x(), financeTab.y());
QThread::msleep(1500);
parser->parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++xmlCounter));
if (parser->isHomeScreen()) return true;
}
QThread::msleep(1000);
parser->parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++xmlCounter));
}
qDebug() << "[js:ozon/helpers] goToAllProducts: not home screen";
return false;
}
void OzonHelpersBridge::createDeviceIfNeeded(const QVariantMap &deviceObj) {
const QString apiId = deviceObj.value("apiId").toString();
if (!apiId.isEmpty()) return;
QJsonObject body;
body["desktop_id"] = SettingsDAO::get("desktop_id");
body["status"] = "ONLINE";
body["name"] = deviceObj.value("name").toString();
body["screen_width"] = deviceObj.value("screenWidth").toInt();
body["screen_height"]= deviceObj.value("screenHeight").toInt();
body["battery"] = deviceObj.value("battery").toInt();
NetworkService ns;
ns.setDeviceId(deviceObj.value("adbSerial").toString());
ns.setPayload(QJsonDocument(body).toJson());
ns.createDevice();
}
void OzonHelpersBridge::postMaterial(const QVariantMap &material, const QString &bankProfileId,
const QString &fullName) {
if (bankProfileId.isEmpty()) return;
if (material.value("cardNumber").toString().isEmpty()) return;
if (!material.value("materialId").toString().isEmpty()) return;
const QMap<QString, int> currencyCodes = {
{"RUB", 643}, {"USD", 840}, {"KRW", 410}, {"AZN", 944}, {"ARS", 32}, {"TJS", 972}
};
QJsonObject body;
body["bank_profile_id"] = bankProfileId;
body["card_number"] = material.value("cardNumber").toString();
body["account_number"] = material.value("accountNumber").toString();
body["name"] = fullName;
body["state"] = material.value("status").toString() == "active" ? "enabled" : "disabled";
body["currency"] = currencyCodes.value(material.value("currency").toString().toUpper(), 643);
NetworkService ns;
ns.setDeviceId(material.value("deviceId").toString());
ns.setPayload(QJsonDocument(body).toJson());
ns.addExtra("account_id", material.value("id").toInt());
ns.postMaterial();
}
bool OzonHelpersBridge::isAppRunning(const QString &deviceId, const QString &packageName) {
return !AdbUtils::tryToGetRunningAppPid(deviceId, packageName).isEmpty();
}
bool OzonHelpersBridge::inputPinCode(const QString &deviceId, const QVariantList &pincodeNodes) {
// Та же логика, что и Ozon::CommonScript::inputPinCode — random delay 100-900мс,
// повторный тап при первом fail.
for (const QVariant &v : pincodeNodes) {
const QVariantMap n = v.toMap();
const int x = n.value("x").toInt();
const int y = n.value("y").toInt();
if (!AdbUtils::makeTap(deviceId, x, y)) {
AdbUtils::makeTap(deviceId, x, y);
}
QThread::msleep(QRandomGenerator::global()->bounded(100, 901));
}
return true;
}
// Та же логика, что FreezeDetector::isUsefulDump в Ozon::CommonScript.
// Полезный дамп должен содержать Button с непустым text ИЛИ EditText.
bool OzonHelpersBridge::isUsefulDump(const QString &xml) {
if (xml.isEmpty()) return false;
if (!xml.contains(QStringLiteral("class=\"android.widget.Button\""))) return false;
static const QRegularExpression usefulRe(
R"(class="android\.widget\.Button"[^/]*text="[^"]+"|text="[^"]+"[^/]*class="android\.widget\.Button"|class="android\.widget\.EditText")"
);
return usefulRe.match(xml).hasMatch();
}
void OzonHelpersBridge::saveTransactionReceipt(const QString &deviceId, int transactionId) {
if (transactionId < 0) return;
const QByteArray screenshot = AdbUtils::captureScreenshotBytes(deviceId);
if (screenshot.isEmpty()) return;
if (!TransactionDAO::updateReceiptImage(transactionId, screenshot)) {
qWarning() << "[js:ozon/helpers] updateReceiptImage failed for txId" << transactionId;
}
}
void OzonHelpersBridge::postNewTransaction(const QVariantMap &accountMap, const QVariantMap &txMap) {
// Зеркало Ozon::CommonScript::postNewTransactionData. Шлёт через
// NetworkService::insertTransactionData в worker-потоке.
auto *thread = new QThread;
auto *worker = new NetworkService;
QJsonObject obj;
obj["id"] = txMap.value("id").toInt();
obj["amount"] = txMap.value("amount").toDouble();
obj["name"] = txMap.value("name").toString();
obj["fee"] = txMap.value("fee").toDouble();
obj["status"] = txMap.value("status").toString();
obj["type"] = txMap.value("type").toString();
obj["phone"] = txMap.value("phone").toString();
obj["description"] = txMap.value("description").toString();
obj["updatedDescription"] = txMap.value("updatedDescription").toString();
obj["bankName"] = txMap.value("bankName").toString();
obj["bankTime"] = txMap.value("bankTime").toString();
obj["bankTransactionId"] = txMap.value("bankTrExternalId").toString();
worker->setDeviceId(accountMap.value("deviceId").toString());
worker->addExtra("lastNumbers", accountMap.value("lastNumbers").toString());
worker->addExtra("transactionId", txMap.value("id").toInt());
worker->setPayload(QJsonDocument(obj).toJson());
worker->moveToThread(thread);
QObject::connect(thread, &QThread::started, worker, &NetworkService::insertTransactionData);
QObject::connect(worker, &NetworkService::finished, thread, &QThread::quit);
QObject::connect(worker, &NetworkService::finished, worker, &QObject::deleteLater);
QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->start();
}
void OzonHelpersBridge::postAccountsData(const QVariantList &accounts) {
if (accounts.isEmpty()) return;
auto *thread = new QThread;
auto *worker = new NetworkService;
QJsonArray list;
QString deviceId;
for (const QVariant &v : accounts) {
const QVariantMap acc = v.toMap();
QJsonObject obj;
obj["appName"] = acc.value("appCode").toString();
obj["lastNumbers"] = acc.value("lastNumbers").toString();
obj["amount"] = acc.value("amount").toDouble();
obj["description"] = acc.value("description").toString();
obj["currency"] = acc.value("currency").toString();
obj["status"] = acc.value("status").toString();
list.append(obj);
if (deviceId.isEmpty()) deviceId = acc.value("deviceId").toString();
}
worker->setDeviceId(deviceId);
worker->setPayload(QJsonDocument(list).toJson());
worker->moveToThread(thread);
QObject::connect(thread, &QThread::started, worker, &NetworkService::postAccountsData);
QObject::connect(worker, &NetworkService::finished, thread, &QThread::quit);
QObject::connect(worker, &NetworkService::finished, worker, &QObject::deleteLater);
QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->start();
}
} // namespace Scripting