1
0
forked from BRT/arc
arc/android/dushanbe/CommonScript.cpp
2026-04-12 15:17:28 +07:00

299 lines
11 KiB
C++
Raw 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 "CommonScript.h"
#include <QJsonArray>
#include <QJsonObject>
#include <QRandomGenerator>
#include <QRegularExpression>
#include <QSettings>
#include <QThread>
#include "adb/AdbUtils.h"
#include "dao/DeviceDAO.h"
#include "dao/BankProfileDAO.h"
#include "net/NetworkService.h"
namespace Dushanbe {
CommonScript::CommonScript(QObject *parent)
: QObject(parent) {
}
CommonScript::~CommonScript() = default;
void CommonScript::start() {
doStart();
emit finished();
}
Node CommonScript::swipeToButton(
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 {};
}
bool CommonScript::inputPinCode(const QString &deviceId, const QString &pinCode) {
if (pinCode.isEmpty()) {
qWarning() << "[Dushanbe::inputPinCode] pinCode is EMPTY — nothing to enter!";
return false;
}
qDebug() << "[Dushanbe::inputPinCode] entering" << pinCode.length() << "digits";
// DC Next использует кнопочную сетку для ввода PIN (content-desc = "0"-"9")
for (const QChar &digit : pinCode) {
Node button = xmlScreenParser.findNodeByContentDesc(QString(digit));
if (button.isEmpty()) {
qWarning() << "[Dushanbe::inputPinCode] Button not found for digit:" << digit;
return false;
}
AdbUtils::makeTap(deviceId, button.x(), button.y());
QThread::msleep(300);
}
return true;
}
bool CommonScript::runAppAndGoToHomeScreen(
const QString &deviceId,
const QString &packageName,
const QString &pinCode,
const int width,
const int height
) {
AdbUtils::unlockScreen(deviceId, width, height);
// Быстрый path: если приложение уже запущено — не перезапускаем сразу,
// а смотрим текущий экран. Если это Home — готово. Если PIN — вводим
// и ждём Home. Только если не смогли — убиваем и рестартим.
if (const QString pid = AdbUtils::tryToGetRunningAppPid(deviceId, packageName); !pid.isEmpty()) {
qDebug() << "[Dushanbe] Process already running, pid:" << pid;
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
if (xmlScreenParser.isHomeScreen()) {
qDebug() << "[Dushanbe] Fast path: already on Home screen";
return true;
}
if (xmlScreenParser.isPinCodeScreen()) {
qDebug() << "[Dushanbe] Fast path: PIN screen, entering PIN";
if (inputPinCode(deviceId, pinCode)) {
QThread::msleep(3000);
for (int i = 0; i < 10; ++i) {
xmlScreenParser.parseAndSaveXml(
AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
if (xmlScreenParser.isHomeScreen()) {
qDebug() << "[Dushanbe] Fast path: Home reached after PIN";
return true;
}
QThread::msleep(1000);
}
}
}
qDebug() << "[Dushanbe] Fast path failed, restarting app";
AdbUtils::tryToKillApplication(deviceId, packageName);
}
if (!AdbUtils::tryToStartApplication(deviceId, packageName)) {
AdbUtils::takeScreenshot(deviceId, "run_app_error");
qWarning() << "[Dushanbe] Process has not started";
return false;
}
QThread::msleep(4500);
int counter = 0;
bool findAndInputPincode = false;
while (true) {
counter++;
if (counter > 20) {
qWarning() << "[Dushanbe] runAppAndGoToHomeScreen: too many attempts, giving up";
AdbUtils::takeScreenshot(deviceId, "too_many_attempts");
return false;
}
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
if (xmlScreenParser.isHomeScreen()) {
qDebug() << "[Dushanbe] Home reached on attempt" << counter;
return true;
}
if (!findAndInputPincode && xmlScreenParser.isPinCodeScreen()) {
qDebug() << "[Dushanbe] PIN screen detected on attempt" << counter << "entering PIN";
if (!inputPinCode(deviceId, pinCode)) {
qWarning() << "[Dushanbe] inputPinCode failed on attempt" << counter;
}
findAndInputPincode = true;
QThread::msleep(3000);
continue;
}
// Диагностика: что реально на экране?
QStringList sample;
for (const Node &n : xmlScreenParser.findAllNodes()) {
if (n.contentDesc.isEmpty() && n.text.isEmpty()) continue;
QString s = !n.contentDesc.isEmpty() ? n.contentDesc : n.text;
s.replace('\n', ' ');
if (s.size() > 60) s = s.left(60) + "...";
sample.append(s);
if (sample.size() >= 8) break;
}
qDebug() << "[Dushanbe] attempt" << counter << "not home, not PIN. Sample:" << sample;
QThread::msleep(1000);
}
}
void CommonScript::postAccountsData(const QList<QPair<MaterialInfo, Node>> &accounts) {
if (accounts.isEmpty()) {
return;
}
auto *thread = new QThread;
auto *worker = new NetworkService;
QJsonArray list;
for (const QPair<MaterialInfo, Node> &pair : accounts) {
QJsonObject obj;
MaterialInfo account = pair.first;
obj["appName"] = account.appCode;
obj["lastNumbers"] = account.lastNumbers;
obj["amount"] = account.amount;
obj["description"] = account.description;
obj["currency"] = account.currency;
obj["status"] = materialStatusToString(account.status);
list.append(obj);
}
worker->setDeviceId(accounts.first().first.deviceId);
worker->setPayload(QJsonDocument(list).toJson());
worker->moveToThread(thread);
connect(thread, &QThread::started, worker, &NetworkService::postAccountsData);
connect(worker, &NetworkService::finished, thread, &QThread::quit);
connect(worker, &NetworkService::finished, worker, &QObject::deleteLater);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->start();
}
void CommonScript::postNewTransactionData(const MaterialInfo &account, const TransactionInfo &transaction) {
auto *thread = new QThread;
auto *worker = new NetworkService;
QJsonObject obj;
obj["id"] = transaction.id;
obj["amount"] = transaction.amount;
obj["name"] = transaction.name;
obj["fee"] = transaction.fee;
obj["status"] = transactionStatusToString(transaction.status);
obj["type"] = transactionTypeToString(transaction.type);
obj["phone"] = transaction.phone;
obj["description"] = transaction.description;
obj["updatedDescription"] = transaction.updatedDescription;
obj["bankName"] = transaction.bankName;
obj["bankTime"] = transaction.bankTime;
obj["bankTransactionId"] = transaction.bankTrExternalId;
worker->setDeviceId(account.deviceId);
worker->addExtra("lastNumbers", account.lastNumbers);
worker->addExtra("transactionId", transaction.id);
worker->setPayload(QJsonDocument(obj).toJson());
worker->moveToThread(thread);
connect(thread, &QThread::started, worker, &NetworkService::insertTransactionData);
connect(worker, &NetworkService::finished, thread, &QThread::quit);
connect(worker, &NetworkService::finished, worker, &QObject::deleteLater);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->start();
}
void CommonScript::updateTransactionData(
const MaterialInfo &account,
const int transactionId,
const TransactionStatus status,
const QString &oldDesc,
const QString &newDesc
) {
auto *thread = new QThread;
auto *worker = new NetworkService;
QJsonObject obj;
obj["id"] = transactionId;
obj["status"] = transactionStatusToString(status);
obj["description"] = newDesc;
obj["updatedDescription"] = oldDesc;
worker->setDeviceId(account.deviceId);
worker->addExtra("lastNumbers", account.lastNumbers);
worker->setPayload(QJsonDocument(obj).toJson());
worker->moveToThread(thread);
connect(thread, &QThread::started, worker, &NetworkService::updateTransactionData);
connect(worker, &NetworkService::finished, thread, &QThread::quit);
connect(worker, &NetworkService::finished, worker, &QObject::deleteLater);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->start();
}
void CommonScript::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() << "[Dushanbe] 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();
qDebug() << "[Dushanbe] createBankProfileIfNeeded: postBankProfile done";
} else {
ns.addExtra("bank_profile_id", app.bankProfileId);
ns.patchBankProfile();
qDebug() << "[Dushanbe] createBankProfileIfNeeded: patchBankProfile done";
}
}
void CommonScript::stop() {
m_running = false;
}
} // namespace Dushanbe