diff --git a/android/black/CommonScript.cpp b/android/black/CommonScript.cpp new file mode 100644 index 0000000..96c05a4 --- /dev/null +++ b/android/black/CommonScript.cpp @@ -0,0 +1,232 @@ +#include "CommonScript.h" + +#include +#include +#include +#include + +#include "adb/AdbUtils.h" +#include "dao/ApplicationDAO.h" +#include "net/NetworkService.h" + +namespace Black { + +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) { + // BlackBanx использует EditText с password=true, а не кнопочную сетку + Node editText = xmlScreenParser.findPinCodeEditText(); + if (editText.isEmpty()) { + qWarning() << "[Black::inputPinCode] PIN EditText not found"; + return false; + } + + // Тапаем по полю для фокуса + AdbUtils::makeTap(deviceId, editText.x(), editText.y()); + QThread::msleep(500); + + // Вводим PIN через текстовый ввод + AdbUtils::inputText(deviceId, pinCode); + QThread::msleep(500); + + return true; +} + +bool CommonScript::runAppAndGoToHomeScreen( + const QString &deviceId, + const QString &packageName, + const QString &pinCode, + const int width, + const int height +) { + // Убить приложение если запущено + if (const QString pid = AdbUtils::tryToGetRunningAppPid(deviceId, packageName); !pid.isEmpty()) { + qDebug() << "[Black] 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() << "[Black] Process has not started"; + return false; + } + + // Даем время на запуск + QThread::msleep(4500); + int counter = 0; + bool findAndInputPincode = false; + + while (true) { + counter++; + + if (counter > 10) { + AdbUtils::takeScreenshot(deviceId, "too_many_attempts"); + return false; + } + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); + + if (xmlScreenParser.isHomeScreen()) { + return true; + } + + // Проверяем экран ввода PIN-кода + if (!findAndInputPincode && xmlScreenParser.isPinCodeScreen()) { + inputPinCode(deviceId, pinCode); + findAndInputPincode = true; + QThread::msleep(3000); + continue; + } + + QThread::msleep(1000); + } +} + +void CommonScript::postAccountsData(const QList> &accounts) { + if (accounts.isEmpty()) { + return; + } + + auto *thread = new QThread; + auto *worker = new NetworkService; + + QJsonArray list; + for (const QPair &pair : accounts) { + QJsonObject obj; + AccountInfo 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"] = accountStatusToString(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 AccountInfo &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 AccountInfo &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) { + const ApplicationInfo app = ApplicationDAO::getApplication(deviceId, appCode); + if (!app.bankProfileId.isEmpty()) { + qDebug() << "[Black] bankProfileId already exists:" << app.bankProfileId; + return; + } + + const QMap currencyCodes = { + {"RUB", 643}, {"USD", 840}, {"KRW", 410}, {"AZN", 944} + }; + const int currencyCode = currencyCodes.value(app.currency.toUpper(), 840); + const QStringList parts = app.fullName.split(' ', Qt::SkipEmptyParts); + + 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"] = app.phone.simplified().remove(' '); + profileBody["balance"] = QString::number(qRound64(app.amount * 100)); + profileBody["state"] = (app.status == "active") ? "enabled" : "disabled"; + profileBody["currency"] = currencyCode; + + NetworkService ns; + ns.setDeviceId(deviceId); + ns.setPayload(QJsonDocument(profileBody).toJson()); + ns.addExtra("app_id", app.id); + ns.postBankProfile(); + qDebug() << "[Black] createBankProfileIfNeeded: postBankProfile done"; +} + +void CommonScript::stop() { + m_running = false; +} + +} // namespace Black diff --git a/android/black/CommonScript.h b/android/black/CommonScript.h new file mode 100644 index 0000000..98d1afb --- /dev/null +++ b/android/black/CommonScript.h @@ -0,0 +1,70 @@ +#pragma once + +#include +#include "ScreenXmlParser.h" +#include "db/TransactionInfo.h" + +namespace Black { + +class CommonScript : public QObject { + Q_OBJECT + +public: + explicit CommonScript(QObject *parent = nullptr); + + ~CommonScript() override; + +protected: + virtual void doStart() = 0; + + // Ввод PIN-кода через EditText (BlackBanx использует текстовое поле, а не кнопки) + bool inputPinCode(const QString &deviceId, const QString &pinCode); + + bool runAppAndGoToHomeScreen( + const QString &deviceId, + const QString &packageName, + const QString &pinCode, + int width, + int height + ); + + Node swipeToButton( + const QString &deviceId, + const QString &text, + int &counter, int width, int height + ); + + void postAccountsData(const QList> &accounts); + + void postNewTransactionData(const AccountInfo &account, const TransactionInfo &transaction); + + void updateTransactionData( + const AccountInfo &account, + int transactionId, + TransactionStatus status, + const QString &oldDesc, + const QString &newDesc + ); + + // Создаёт bank_profile на сервере если ещё не создан + void createBankProfileIfNeeded(const QString &deviceId, const QString &appCode); + + bool m_running = true; + ScreenXmlParser xmlScreenParser; + int m_counter = 0; + +signals: + void finished(); + + void finishedWithResult(const QString &result); + +public slots: + virtual void start() final; + + virtual void stop() final; + +private: + Q_DISABLE_COPY(CommonScript); +}; + +} // namespace Black diff --git a/android/black/GetCardInfoScript.cpp b/android/black/GetCardInfoScript.cpp new file mode 100644 index 0000000..c27621c --- /dev/null +++ b/android/black/GetCardInfoScript.cpp @@ -0,0 +1,132 @@ +#include "GetCardInfoScript.h" + +#include + +#include "adb/AdbUtils.h" +#include "dao/AccountDAO.h" +#include "dao/ApplicationDAO.h" +#include "dao/DeviceDAO.h" + +namespace Black { + +GetCardInfoScript::GetCardInfoScript( + QString deviceId, + QString appCode, + QObject *parent +) : CommonScript(parent), + m_deviceId(std::move(deviceId)), + m_appCode(std::move(appCode)) { +} + +GetCardInfoScript::~GetCardInfoScript() = default; + +void GetCardInfoScript::doStart() { + const DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId); + const ApplicationInfo app = ApplicationDAO::getApplication(m_deviceId, m_appCode); + + if (device.id.isEmpty() || app.id == -1) { + m_error = "Device or app not found: " + m_deviceId + " " + m_appCode; + qCritical() << m_error; + emit finishedWithResult(m_error); + return; + } + + // Читаем текущий экран + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); + + // Если не на домашнем экране — перезапускаем + if (!xmlScreenParser.isHomeScreen()) { + qDebug() << "[Black::GetCardInfo] Not on home screen, restarting app..."; + if (!runAppAndGoToHomeScreen(device.id, app.package, app.pinCode, + device.screenWidth, device.screenHeight)) { + m_error = "Cannot reach home screen: " + m_deviceId + " " + m_appCode; + qWarning() << m_error; + AdbUtils::tryToKillApplication(m_deviceId, app.package); + emit finishedWithResult(m_error); + return; + } + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); + } + + // 1. Парсим баланс с домашнего экрана + const double balance = xmlScreenParser.parseBalanceFromHomeScreen(); + qDebug() << "[Black::GetCardInfo] balance from home:" << balance; + ApplicationDAO::updateAmount(app.id, balance); + + // 2. Нажимаем "Tu CVU" на домашнем экране + Node cvuButton = xmlScreenParser.findButtonNode("Tu CVU", false); + if (cvuButton.isEmpty()) { + m_error = "Cannot find Tu CVU button: " + m_deviceId; + qWarning() << m_error; + AdbUtils::tryToKillApplication(m_deviceId, app.package); + emit finishedWithResult(m_error); + return; + } + + AdbUtils::makeTap(m_deviceId, cvuButton.x(), cvuButton.y()); + QThread::msleep(2000); + + // 3. Ждём экран CVU + bool cvuScreenFound = false; + for (int attempt = 0; attempt < 5; ++attempt) { + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); + if (xmlScreenParser.isCVUScreen()) { + cvuScreenFound = true; + break; + } + QThread::msleep(1000); + } + + if (!cvuScreenFound) { + m_error = "CVU screen not detected: " + m_deviceId; + qWarning() << m_error; + AdbUtils::tryToKillApplication(m_deviceId, app.package); + emit finishedWithResult(m_error); + return; + } + + // 4. Парсим номер CVU + const QString cvuNumber = xmlScreenParser.parseCVUNumber(); + qDebug() << "[Black::GetCardInfo] CVU number:" << cvuNumber; + + if (cvuNumber.isEmpty()) { + m_error = "Cannot parse CVU number: " + m_deviceId; + qWarning() << m_error; + AdbUtils::tryToKillApplication(m_deviceId, app.package); + emit finishedWithResult(m_error); + return; + } + + // 5. Сохраняем аккаунт в БД (lastNumbers = последние 4 цифры CVU) + const QString lastNumbers = cvuNumber.right(4); + + AccountInfo account; + account.deviceId = m_deviceId; + account.appCode = m_appCode; + account.lastNumbers = lastNumbers; + account.cardNumber = cvuNumber; + account.amount = balance; + account.currency = "USD"; + account.description = "CVU"; + account.status = AccountStatus::Active; + account.updateTime = QDateTime::currentDateTimeUtc(); + + if (!AccountDAO::upsertAccount(account)) { + qWarning() << "[Black::GetCardInfo] Failed to upsert account"; + } + + // 6. Отправляем данные аккаунтов на сервер + AccountInfo savedAccount = AccountDAO::findAppAccount(m_deviceId, m_appCode, lastNumbers); + if (savedAccount.id != -1) { + QList> accounts; + accounts.append({savedAccount, Node()}); + postAccountsData(accounts); + } + + qDebug() << "[Black::GetCardInfo] Done. CVU:" << cvuNumber << "balance:" << balance; + + AdbUtils::tryToKillApplication(m_deviceId, app.package); + emit finishedWithResult(m_error); +} + +} // namespace Black diff --git a/android/black/GetCardInfoScript.h b/android/black/GetCardInfoScript.h new file mode 100644 index 0000000..978e5b2 --- /dev/null +++ b/android/black/GetCardInfoScript.h @@ -0,0 +1,27 @@ +#pragma once +#include "CommonScript.h" + +namespace Black { + +class GetCardInfoScript final : public CommonScript { + Q_OBJECT + +public: + explicit GetCardInfoScript( + QString deviceId, + QString appCode, + QObject *parent = nullptr + ); + + ~GetCardInfoScript() override; + +protected: + void doStart() override; + +private: + const QString m_deviceId; + const QString m_appCode; + QString m_error; +}; + +} // namespace Black diff --git a/android/black/GetProfileInfoScript.cpp b/android/black/GetProfileInfoScript.cpp new file mode 100644 index 0000000..651d082 --- /dev/null +++ b/android/black/GetProfileInfoScript.cpp @@ -0,0 +1,168 @@ +#include "GetProfileInfoScript.h" + +#include +#include +#include + +#include "adb/AdbUtils.h" +#include "dao/ApplicationDAO.h" +#include "dao/DeviceDAO.h" +#include "net/NetworkService.h" + +namespace Black { + +GetProfileInfoScript::GetProfileInfoScript( + QString deviceId, + QString appCode, + QObject *parent +) : CommonScript(parent), + m_deviceId(std::move(deviceId)), + m_appCode(std::move(appCode)) { +} + +GetProfileInfoScript::~GetProfileInfoScript() = default; + +void GetProfileInfoScript::doStart() { + const DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId); + const ApplicationInfo app = ApplicationDAO::getApplication(m_deviceId, m_appCode); + + if (device.id.isEmpty() || app.id == -1) { + m_error = "Device or app not found: " + m_deviceId + " " + m_appCode; + qCritical() << m_error; + emit finishedWithResult(m_error); + return; + } + + // Читаем текущий экран + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); + + // Если не на домашнем экране — перезапускаем + if (!xmlScreenParser.isHomeScreen()) { + qDebug() << "[Black::GetProfileInfo] Not on home screen, restarting app..."; + if (!runAppAndGoToHomeScreen(device.id, app.package, app.pinCode, + device.screenWidth, device.screenHeight)) { + m_error = "Cannot reach home screen: " + m_deviceId + " " + m_appCode; + qWarning() << m_error; + AdbUtils::tryToKillApplication(m_deviceId, app.package); + emit finishedWithResult(m_error); + return; + } + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); + } + + // 1. Тапаем по кнопке "¡Hola!" (аватар с именем) на домашнем экране + Node holaNode = xmlScreenParser.findHolaButton(); + if (holaNode.isEmpty()) { + m_error = "Cannot find Hola button on home screen: " + m_deviceId; + qWarning() << m_error; + AdbUtils::tryToKillApplication(m_deviceId, app.package); + emit finishedWithResult(m_error); + return; + } + + // Парсим имя и баланс с домашнего экрана + const QString fullName = xmlScreenParser.parseUserNameFromHomeScreen(); + const double balance = xmlScreenParser.parseBalanceFromHomeScreen(); + qDebug() << "[Black::GetProfileInfo] fullName from home:" << fullName << "balance:" << balance; + + // Сохраняем баланс + ApplicationDAO::updateAmount(app.id, balance); + + AdbUtils::makeTap(m_deviceId, holaNode.x(), holaNode.y()); + QThread::msleep(2000); + + // 2. Ждём боковое меню + bool sideMenuFound = false; + for (int attempt = 0; attempt < 5; ++attempt) { + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); + if (xmlScreenParser.isSideMenu()) { + sideMenuFound = true; + break; + } + QThread::msleep(1000); + } + + if (!sideMenuFound) { + m_error = "Side menu not detected: " + m_deviceId; + qWarning() << m_error; + AdbUtils::tryToKillApplication(m_deviceId, app.package); + emit finishedWithResult(m_error); + return; + } + + // 3. Тапаем "Tu Información" + Node tuInfoNode = xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("Tu Información")); + if (tuInfoNode.isEmpty()) { + m_error = "Cannot find Tu Información button: " + m_deviceId; + qWarning() << m_error; + AdbUtils::tryToKillApplication(m_deviceId, app.package); + emit finishedWithResult(m_error); + return; + } + + AdbUtils::makeTap(m_deviceId, tuInfoNode.x(), tuInfoNode.y()); + QThread::msleep(2500); + + // 4. Ждём страницу профиля "Tu información" + bool profileFound = false; + for (int attempt = 0; attempt < 5; ++attempt) { + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); + if (xmlScreenParser.isProfilePage()) { + profileFound = true; + break; + } + QThread::msleep(1000); + } + + if (!profileFound) { + m_error = "Profile page not detected: " + m_deviceId; + qWarning() << m_error; + AdbUtils::tryToKillApplication(m_deviceId, app.package); + emit finishedWithResult(m_error); + return; + } + + // 5. Парсим данные профиля (первый экран: nombre, apellido, email) + const QString nombre = xmlScreenParser.parseProfileNombre(); + const QString apellido = xmlScreenParser.parseProfileApellido(); + const QString email = xmlScreenParser.parseProfileEmail(); + + // 6. Скроллим вниз чтобы увидеть телефон + const int x = device.screenWidth / 2; + const int fromY = device.screenHeight * 3 / 4; + const int toY = device.screenHeight / 4; + AdbUtils::makeSwipe(m_deviceId, x, fromY, x, toY); + QThread::msleep(1500); + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); + + const QString phone = xmlScreenParser.parseProfilePhone(); + + // Собираем полное имя: "имя фамилия" + QString parsedFullName; + if (!nombre.isEmpty() && !apellido.isEmpty()) { + parsedFullName = nombre + " " + apellido; + } else if (!nombre.isEmpty()) { + parsedFullName = nombre; + } else { + parsedFullName = fullName; // fallback с домашнего экрана + } + + qDebug() << "[Black::GetProfileInfo] nombre:" << nombre + << "apellido:" << apellido + << "email:" << email + << "phone:" << phone + << "fullName:" << parsedFullName; + + // 7. Сохраняем в БД + if (!ApplicationDAO::updateProfileInfo(app.id, parsedFullName, phone, email)) { + qWarning() << "[Black::GetProfileInfo] Failed to save profile info to DB"; + } + + // 7. Создаём bank profile если нет + createBankProfileIfNeeded(m_deviceId, m_appCode); + + AdbUtils::tryToKillApplication(m_deviceId, app.package); + emit finishedWithResult(m_error); +} + +} // namespace Black diff --git a/android/black/GetProfileInfoScript.h b/android/black/GetProfileInfoScript.h new file mode 100644 index 0000000..94b4883 --- /dev/null +++ b/android/black/GetProfileInfoScript.h @@ -0,0 +1,27 @@ +#pragma once +#include "CommonScript.h" + +namespace Black { + +class GetProfileInfoScript final : public CommonScript { + Q_OBJECT + +public: + ~GetProfileInfoScript() override; + + explicit GetProfileInfoScript( + QString deviceId, + QString appCode, + QObject *parent = nullptr + ); + +protected: + void doStart() override; + +private: + const QString m_deviceId; + const QString m_appCode; + QString m_error; +}; + +} // namespace Black diff --git a/android/black/LoginAndCheckAccountsScript.cpp b/android/black/LoginAndCheckAccountsScript.cpp new file mode 100644 index 0000000..790b80a --- /dev/null +++ b/android/black/LoginAndCheckAccountsScript.cpp @@ -0,0 +1,64 @@ +#include "LoginAndCheckAccountsScript.h" + +#include + +#include "adb/AdbUtils.h" +#include "dao/ApplicationDAO.h" +#include "dao/DeviceDAO.h" +#include "AppLogger.h" +#include "GetProfileInfoScript.h" + +namespace Black { + +LoginAndCheckAccountsScript::LoginAndCheckAccountsScript( + QString deviceId, + QString appCode, + QObject *parent +) : CommonScript(parent), + m_deviceId(std::move(deviceId)), + m_appCode(std::move(appCode)) { +} + +LoginAndCheckAccountsScript::~LoginAndCheckAccountsScript() = default; + +void LoginAndCheckAccountsScript::doStart() { + const DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId); + const ApplicationInfo app = ApplicationDAO::getApplication(m_deviceId, m_appCode); + + if (device.id.isEmpty() || app.id == -1) { + m_error = "Device or app not found: " + m_deviceId + " " + m_appCode; + qCritical() << m_error; + emit finishedWithResult(m_error); + return; + } + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); + + if (!runAppAndGoToHomeScreen(device.id, app.package, app.pinCode, device.screenWidth, device.screenHeight)) { + m_error = "Cant open the home screen: " + m_deviceId + " " + m_appCode; + qWarning() << m_error; + AppLogger::log("black/LoginAndCheck", m_deviceId, m_error, + AdbUtils::captureScreenshotBytes(m_deviceId), + "FETCH_PROFILE"); + if (!ApplicationDAO::updatePinCodeStatus(app.id, "no", "Не прошла проверка [дата]")) { + qWarning() << "Cant update the pincode: " << m_deviceId << " " << m_appCode; + } + + AdbUtils::tryToKillApplication(m_deviceId, app.package); + emit finishedWithResult(m_error); + return; + } + + // PIN-код прошёл + if (!ApplicationDAO::updatePinCodeStatus(app.id, "yes", "")) { + qWarning() << "Cant update the pincode: " << m_deviceId << " " << m_appCode; + } + + // Проверяем профиль и аккаунты (приложение уже на home screen) + GetProfileInfoScript profileScript(m_deviceId, m_appCode); + profileScript.start(); + + emit finishedWithResult(m_error); +} + +} // namespace Black diff --git a/android/black/LoginAndCheckAccountsScript.h b/android/black/LoginAndCheckAccountsScript.h new file mode 100644 index 0000000..a3a9f0c --- /dev/null +++ b/android/black/LoginAndCheckAccountsScript.h @@ -0,0 +1,27 @@ +#pragma once +#include "CommonScript.h" + +namespace Black { + +class LoginAndCheckAccountsScript final : public CommonScript { + Q_OBJECT + +public: + ~LoginAndCheckAccountsScript() override; + + explicit LoginAndCheckAccountsScript( + QString deviceId, + QString appCode, + QObject *parent = nullptr + ); + +protected: + void doStart() override; + +private: + const QString m_deviceId; + const QString m_appCode; + QString m_error; +}; + +} // namespace Black diff --git a/android/black/ScreenXmlParser.cpp b/android/black/ScreenXmlParser.cpp new file mode 100644 index 0000000..ac26d93 --- /dev/null +++ b/android/black/ScreenXmlParser.cpp @@ -0,0 +1,309 @@ +#include "ScreenXmlParser.h" + +#include +#include +#include + +#include "android/xml/Node.h" + +namespace Black { + +static const QRegularExpression boundsRegex(R"(\[([0-9]+),([0-9]+)\]\[([0-9]+),([0-9]+)\])"); + +ScreenXmlParser::ScreenXmlParser(QObject *parent) : QObject(parent) { +} + +ScreenXmlParser::~ScreenXmlParser() = default; + +void ScreenXmlParser::parseAndSaveXml(const QString &xml) { + QDomDocument doc; + doc.setContent(xml); + m_xml = doc.documentElement(); + m_nodes.clear(); + + QXmlStreamReader reader(xml); + while (!reader.atEnd()) { + reader.readNext(); + if (reader.isStartElement() && reader.name() == u"node") { + Node node; + if (reader.attributes().hasAttribute("bounds")) { + QString bounds = reader.attributes().value("bounds").toString(); + if (QRegularExpressionMatch match = boundsRegex.match(bounds); match.hasMatch()) { + node.setBounds( + match.captured(1).toInt(), + match.captured(2).toInt(), + match.captured(3).toInt(), + match.captured(4).toInt() + ); + } + } + if (reader.attributes().hasAttribute("text")) { + node.text = reader.attributes().value("text").toString(); + } + if (reader.attributes().hasAttribute("content-desc")) { + node.contentDesc = reader.attributes().value("content-desc").toString(); + } + if (reader.attributes().hasAttribute("class")) { + node.className = reader.attributes().value("class").toString(); + } + if (reader.attributes().hasAttribute("clickable")) { + node.clickable = reader.attributes().value("clickable").toString() == "true"; + } + if (reader.attributes().hasAttribute("focusable")) { + node.focusable = reader.attributes().value("focusable").toString() == "true"; + } + if (reader.attributes().hasAttribute("resource-id")) { + node.resourceId = reader.attributes().value("resource-id").toString(); + } + if (reader.attributes().hasAttribute("hint")) { + node.hint = reader.attributes().value("hint").toString(); + } + if (reader.attributes().hasAttribute("password")) { + node.password = reader.attributes().value("password").toString() == "true"; + } + m_nodes.append(node); + } + } +} + +const QList &ScreenXmlParser::findAllNodes() const { + return m_nodes; +} + +Node ScreenXmlParser::findTextNode(const QString &text) { + for (const Node &node : m_nodes) { + if (node.text.trimmed() == text) { + return node; + } + } + return {}; +} + +Node ScreenXmlParser::findNodeByContentDesc(const QString &contentDesc) { + for (const Node &node : m_nodes) { + if (node.contentDesc == contentDesc) { + return node; + } + } + return {}; +} + +Node ScreenXmlParser::findNodeByContentDesc(const QString &contentDesc, bool contains) { + for (const Node &node : m_nodes) { + if (contains) { + if (node.contentDesc.contains(contentDesc)) { + return node; + } + } else { + if (node.contentDesc == contentDesc) { + return node; + } + } + } + return {}; +} + +Node ScreenXmlParser::findButtonNode(const QString &text, bool contains) { + for (const Node &node : m_nodes) { + if (!node.clickable) continue; + if (contains) { + if (node.text.contains(text) || node.contentDesc.contains(text)) { + return node; + } + } else { + if (node.text.trimmed() == text || node.contentDesc == text) { + return node; + } + } + } + return {}; +} + +Node ScreenXmlParser::findFirstEditText() { + for (const Node &node : m_nodes) { + if (node.className == "android.widget.EditText") { + return node; + } + } + return {}; +} + +bool ScreenXmlParser::isPinCodeScreen() { + for (const Node &node : m_nodes) { + if (node.contentDesc.contains(QString::fromUtf8("¿Olvidaste tu PIN?"))) { + return true; + } + if (node.contentDesc.contains(QString::fromUtf8("Ingresá tu PIN"))) { + return true; + } + } + return false; +} + +Node ScreenXmlParser::findPinCodeEditText() { + for (const Node &node : m_nodes) { + if (node.className == "android.widget.EditText" && node.password) { + return node; + } + } + return {}; +} + +bool ScreenXmlParser::isHomeScreen() { + bool hasSaldo = false; + bool hasInicio = false; + for (const Node &node : m_nodes) { + if (node.contentDesc == "Saldo disponible") { + hasSaldo = true; + } + if (node.contentDesc == "Inicio") { + hasInicio = true; + } + if (hasSaldo && hasInicio) { + return true; + } + } + return false; +} + +bool ScreenXmlParser::isSideMenu() { + bool hasTuInfo = false; + bool hasCerrar = false; + for (const Node &node : m_nodes) { + if (node.contentDesc == QString::fromUtf8("Tu Información")) { + hasTuInfo = true; + } + if (node.contentDesc == QString::fromUtf8("Cerrar sesión")) { + hasCerrar = true; + } + if (hasTuInfo && hasCerrar) { + return true; + } + } + return false; +} + +Node ScreenXmlParser::findHolaButton() { + for (const Node &node : m_nodes) { + if (node.clickable && node.contentDesc.startsWith(QString::fromUtf8("¡Hola!"))) { + return node; + } + } + return {}; +} + +QString ScreenXmlParser::parseUserNameFromHomeScreen() { + // content-desc: "¡Hola!\nveronica cintia heredia \nvc" + for (const Node &node : m_nodes) { + if (node.contentDesc.startsWith(QString::fromUtf8("¡Hola!"))) { + QStringList lines = node.contentDesc.split('\n'); + if (lines.size() >= 2) { + return lines[1].trimmed(); + } + } + } + return {}; +} + +double ScreenXmlParser::parseBalanceFromHomeScreen() { + // Баланс идёт после "Saldo disponible", формат: "$1.857,00" или "$0,00" + bool afterSaldo = false; + for (const Node &node : m_nodes) { + if (node.contentDesc == "Saldo disponible") { + afterSaldo = true; + continue; + } + if (afterSaldo && node.contentDesc.startsWith("$")) { + // "$1.857,00" → "1857.00" + QString raw = node.contentDesc.mid(1); // убираем "$" + raw.remove('.'); // убираем разделитель тысяч + raw.replace(',', '.'); // запятая → точка + bool ok = false; + double val = raw.toDouble(&ok); + if (ok) return val; + } + } + return 0.0; +} + +bool ScreenXmlParser::isProfilePage() { + for (const Node &node : m_nodes) { + if (node.contentDesc == QString::fromUtf8("Tu información")) { + return true; + } + } + return false; +} + +QString ScreenXmlParser::parseProfileNombre() { + for (const Node &node : m_nodes) { + if (node.hint == "Nombre" && !node.text.isEmpty()) { + return node.text.trimmed(); + } + } + return {}; +} + +QString ScreenXmlParser::parseProfileApellido() { + for (const Node &node : m_nodes) { + if (node.hint == "Apellido" && !node.text.isEmpty()) { + return node.text.trimmed(); + } + } + return {}; +} + +QString ScreenXmlParser::parseProfileEmail() { + for (const Node &node : m_nodes) { + if (node.hint == "E-mail" && !node.text.isEmpty()) { + return node.text.trimmed(); + } + } + return {}; +} + +QString ScreenXmlParser::parseProfilePhone() { + for (const Node &node : m_nodes) { + if (node.hint == QString::fromUtf8("Teléfono celular") && !node.text.isEmpty()) { + return node.text.trimmed(); + } + } + return {}; +} + +bool ScreenXmlParser::isCVUScreen() { + for (const Node &node : m_nodes) { + if (node.contentDesc == "CVU") { + return true; + } + } + return false; +} + +QString ScreenXmlParser::parseCVUNumber() { + // CVU номер идёт после content-desc="CVU", это 22-значный номер + bool afterCVU = false; + for (const Node &node : m_nodes) { + if (node.contentDesc == "CVU") { + afterCVU = true; + continue; + } + if (afterCVU && !node.contentDesc.isEmpty()) { + // Проверяем что это числовая строка (CVU — 22 цифры) + QString cvu = node.contentDesc.trimmed(); + bool allDigits = true; + for (const QChar &c : cvu) { + if (!c.isDigit()) { + allDigits = false; + break; + } + } + if (allDigits && !cvu.isEmpty()) { + return cvu; + } + } + } + return {}; +} + +} // namespace Black diff --git a/android/black/ScreenXmlParser.h b/android/black/ScreenXmlParser.h new file mode 100644 index 0000000..5d55c32 --- /dev/null +++ b/android/black/ScreenXmlParser.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include "android/xml/Node.h" +#include "db/AccountInfo.h" + +namespace Black { + +class ScreenXmlParser final : public QObject { + Q_OBJECT + +public: + explicit ScreenXmlParser(QObject *parent = nullptr); + + ~ScreenXmlParser() override; + + void parseAndSaveXml(const QString &xml); + + const QList &findAllNodes() const; + + Node findTextNode(const QString &text); + + Node findNodeByContentDesc(const QString &contentDesc); + + Node findNodeByContentDesc(const QString &contentDesc, bool contains); + + Node findButtonNode(const QString &text, bool contains); + + Node findFirstEditText(); + + // Определяет экран ввода PIN-кода по content-desc "¿Olvidaste tu PIN?" + bool isPinCodeScreen(); + + // Находит EditText с password=true для ввода PIN + Node findPinCodeEditText(); + + bool isHomeScreen(); + + // Определяет боковое меню по content-desc "Tu Información" + "Cerrar sesión" + bool isSideMenu(); + + // Находит кнопку "¡Hola!" (аватар с именем) на домашнем экране + Node findHolaButton(); + + // Парсит имя пользователя из content-desc "¡Hola!\nимя\nинициалы" + QString parseUserNameFromHomeScreen(); + + // Парсит баланс с домашнего экрана (content-desc "$0,00" после "Saldo disponible") + double parseBalanceFromHomeScreen(); + + // Определяет страницу профиля "Tu información" + bool isProfilePage(); + + // Парсит данные профиля по hint полей + QString parseProfileNombre(); // hint="Nombre" + QString parseProfileApellido(); // hint="Apellido" + QString parseProfileEmail(); // hint="E-mail" + QString parseProfilePhone(); // hint="Teléfono celular" + + // Определяет экран CVU (окно поверх домашнего экрана) + bool isCVUScreen(); + + // Парсит номер CVU (22-значный номер после content-desc="CVU") + QString parseCVUNumber(); + +private: + QList m_nodes; + QDomElement m_xml; +}; + +} // namespace Black diff --git a/android/ozon/CommonScript.cpp b/android/ozon/CommonScript.cpp index c20daa3..28aebe8 100644 --- a/android/ozon/CommonScript.cpp +++ b/android/ozon/CommonScript.cpp @@ -6,6 +6,7 @@ #include #include "adb/AdbUtils.h" +#include "dao/ApplicationDAO.h" #include "net/NetworkService.h" namespace Ozon { @@ -289,6 +290,37 @@ void CommonScript::updateTransactionData( thread->start(); } +void CommonScript::createBankProfileIfNeeded(const QString &deviceId, const QString &appCode) { + const ApplicationInfo app = ApplicationDAO::getApplication(deviceId, appCode); + if (!app.bankProfileId.isEmpty()) { + qDebug() << "[Ozon] bankProfileId already exists:" << app.bankProfileId; + return; + } + + const QMap currencyCodes = { + {"RUB", 643}, {"USD", 840}, {"KRW", 410}, {"AZN", 944} + }; + const int currencyCode = currencyCodes.value(app.currency.toUpper(), 643); + const QStringList parts = app.fullName.split(' ', Qt::SkipEmptyParts); + + 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"] = app.phone.simplified().remove(' '); + profileBody["balance"] = QString::number(qRound64(app.amount * 100)); + profileBody["state"] = (app.status == "active") ? "enabled" : "disabled"; + profileBody["currency"] = currencyCode; + + NetworkService ns; + ns.setDeviceId(deviceId); + ns.setPayload(QJsonDocument(profileBody).toJson()); + ns.addExtra("app_id", app.id); + ns.postBankProfile(); + qDebug() << "[Ozon] createBankProfileIfNeeded: postBankProfile done"; +} + void CommonScript::stop() { m_running = false; } diff --git a/android/ozon/CommonScript.h b/android/ozon/CommonScript.h index 75d888f..eef6b9b 100644 --- a/android/ozon/CommonScript.h +++ b/android/ozon/CommonScript.h @@ -57,6 +57,9 @@ protected: const QString &deviceId, int width, int height ); + // Создаёт bank_profile на сервере если ещё не создан + void createBankProfileIfNeeded(const QString &deviceId, const QString &appCode); + bool m_running = true; ScreenXmlParser xmlScreenParser; int m_counter = 0; diff --git a/android/ozon/LoginAndCheckAccountsScript.cpp b/android/ozon/LoginAndCheckAccountsScript.cpp index 71a4589..045f6a4 100644 --- a/android/ozon/LoginAndCheckAccountsScript.cpp +++ b/android/ozon/LoginAndCheckAccountsScript.cpp @@ -53,6 +53,10 @@ void LoginAndCheckAccountsScript::doStart() { if (!ApplicationDAO::updatePinCodeStatus(app.id, "yes", "")) { qWarning() << "Cant update the pincode: " << m_deviceId << " " << m_appCode; } + + // Создаём bank profile если ещё не создан + createBankProfileIfNeeded(m_deviceId, m_appCode); + if (goToAllProducts(device.id, device.screenWidth, device.screenHeight)) { QList > accounts = xmlScreenParser.parseAccountsInfo(); for (auto &[account, node]: accounts) { diff --git a/assets/black/accounts.png b/assets/black/accounts.png new file mode 100644 index 0000000..d33cf8a Binary files /dev/null and b/assets/black/accounts.png differ diff --git a/assets/black/accounts.xml b/assets/black/accounts.xml new file mode 100644 index 0000000..8e5ab51 --- /dev/null +++ b/assets/black/accounts.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/black/home_screen.png b/assets/black/home_screen.png new file mode 100644 index 0000000..dfb18f6 Binary files /dev/null and b/assets/black/home_screen.png differ diff --git a/assets/black/home_screen.xml b/assets/black/home_screen.xml new file mode 100644 index 0000000..3b6fc62 --- /dev/null +++ b/assets/black/home_screen.xml @@ -0,0 +1,248 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/black/pin_code.png b/assets/black/pin_code.png new file mode 100644 index 0000000..b3647ff Binary files /dev/null and b/assets/black/pin_code.png differ diff --git a/assets/black/pin_code.xml b/assets/black/pin_code.xml new file mode 100644 index 0000000..72c21dc --- /dev/null +++ b/assets/black/pin_code.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/black/profile_page.png b/assets/black/profile_page.png new file mode 100644 index 0000000..9d55147 Binary files /dev/null and b/assets/black/profile_page.png differ diff --git a/assets/black/profile_page.xml b/assets/black/profile_page.xml new file mode 100644 index 0000000..0911197 --- /dev/null +++ b/assets/black/profile_page.xml @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/black/profile_page_2.png b/assets/black/profile_page_2.png new file mode 100644 index 0000000..708799b Binary files /dev/null and b/assets/black/profile_page_2.png differ diff --git a/assets/black/profile_page_2.xml b/assets/black/profile_page_2.xml new file mode 100644 index 0000000..14df9dc --- /dev/null +++ b/assets/black/profile_page_2.xml @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/black/side_menu.png b/assets/black/side_menu.png new file mode 100644 index 0000000..8e05b93 Binary files /dev/null and b/assets/black/side_menu.png differ diff --git a/assets/black/side_menu.xml b/assets/black/side_menu.xml new file mode 100644 index 0000000..d94bd68 --- /dev/null +++ b/assets/black/side_menu.xml @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config.ini b/config.ini index 44e6dfd..f7e3166 100644 --- a/config.ini +++ b/config.ini @@ -1,5 +1,5 @@ [apps] -list=ozon +list=ozon, black [common] currencies=USD, KRW, AZN, RUB @@ -25,6 +25,11 @@ paymentsGet=http://localhost:9999/api/v1/payments transactionInsert=http://localhost:9999/api/v1/transaction/insert transactionUpdate=http://localhost:9999/api/v1/transaction/update +[black] +android_package_name=app.blackwallet.ar +currency=USD +name=Black + [ozon] android_package_name=ru.ozon.fintech.finance currency=RUB diff --git a/database/DatabaseManager.cpp b/database/DatabaseManager.cpp index 8d79a82..a86a70d 100644 --- a/database/DatabaseManager.cpp +++ b/database/DatabaseManager.cpp @@ -128,6 +128,7 @@ void DatabaseManager::initSchema() { install TEXT DEFAULT 'no', comment TEXT, phone TEXT DEFAULT '', + email TEXT DEFAULT '', full_name TEXT DEFAULT '', bank_profile_id TEXT DEFAULT '', currency TEXT DEFAULT '', @@ -223,6 +224,15 @@ void DatabaseManager::initSchema() { q.exec("PRAGMA user_version = 1"); qDebug() << "[DB] Migration 1 applied"; } + + // ── Версия 2: добавляем email в applications ───────────────────────────── + if (version < 2) { + db.transaction(); + q.exec("ALTER TABLE applications ADD COLUMN email TEXT DEFAULT ''"); + db.commit(); + q.exec("PRAGMA user_version = 2"); + qDebug() << "[DB] Migration 2 applied"; + } } void DatabaseManager::closeConnection() { diff --git a/database/app_data.db b/database/app_data.db index e7eeb21..d995045 100644 Binary files a/database/app_data.db and b/database/app_data.db differ diff --git a/database/dao/AccountDAO.cpp b/database/dao/AccountDAO.cpp index 5a82a26..09bd502 100644 --- a/database/dao/AccountDAO.cpp +++ b/database/dao/AccountDAO.cpp @@ -10,13 +10,14 @@ bool AccountDAO::upsertAccount(const AccountInfo &info) { QSqlQuery query(DatabaseManager::instance().database()); query.prepare(R"( - INSERT INTO accounts (device_id, app_code, last_numbers, amount, update_time, status, description, currency) - VALUES (:device_id, :app_code, :last_numbers, :amount, :update_time, :status, :description, :currency) + INSERT INTO accounts (device_id, app_code, last_numbers, card_number, amount, update_time, status, description, currency) + VALUES (:device_id, :app_code, :last_numbers, :card_number, :amount, :update_time, :status, :description, :currency) ON CONFLICT(device_id, app_code, last_numbers) DO UPDATE SET amount = excluded.amount, update_time = excluded.update_time, last_numbers = excluded.last_numbers, + card_number = excluded.card_number, status = excluded.status, description = excluded.description, currency = excluded.currency @@ -25,6 +26,7 @@ bool AccountDAO::upsertAccount(const AccountInfo &info) { query.bindValue(":device_id", info.deviceId); query.bindValue(":app_code", info.appCode); query.bindValue(":last_numbers", info.lastNumbers); + query.bindValue(":card_number", info.cardNumber); query.bindValue(":currency", info.currency); query.bindValue(":description", info.description); query.bindValue(":amount", info.amount); diff --git a/database/dao/ApplicationDAO.cpp b/database/dao/ApplicationDAO.cpp index 78f688b..d0ff368 100644 --- a/database/dao/ApplicationDAO.cpp +++ b/database/dao/ApplicationDAO.cpp @@ -128,6 +128,7 @@ ApplicationInfo parseApplication(const QSqlQuery &query) { app.install = query.value("install").toString() == "yes"; app.pinCodeChecked = query.value("pin_code_checked").toString() == "yes"; app.phone = query.value("phone").toString(); + app.email = query.value("email").toString(); app.fullName = query.value("full_name").toString(); app.bankProfileId = query.value("bank_profile_id").toString(); app.currency = query.value("currency").toString(); @@ -139,7 +140,7 @@ ApplicationInfo ApplicationDAO::getApplication(const QString &deviceId, const QS QSqlQuery query(DatabaseManager::instance().database()); query.prepare(R"( - SELECT id, device_id, pin_code, code, name, package, status, comment, install, pin_code_checked, phone, full_name, bank_profile_id, currency, amount + SELECT id, device_id, pin_code, code, name, package, status, comment, install, pin_code_checked, phone, email, full_name, bank_profile_id, currency, amount FROM applications WHERE device_id = :device_id AND code = :code LIMIT 1 @@ -164,7 +165,7 @@ QList ApplicationDAO::getApplicationsByDeviceId(const QString & QList list; query.prepare(R"( - SELECT id, device_id, pin_code, code, name, package, status, comment, install, pin_code_checked, phone, full_name, bank_profile_id, currency, amount + SELECT id, device_id, pin_code, code, name, package, status, comment, install, pin_code_checked, phone, email, full_name, bank_profile_id, currency, amount FROM applications WHERE device_id = :device_id AND install = 'yes' )"); @@ -185,19 +186,21 @@ QList ApplicationDAO::getApplicationsByDeviceId(const QString & bool ApplicationDAO::updateProfileInfo( const int &appId, const QString &fullName, - const QString &phone + const QString &phone, + const QString &email ) { QSqlQuery query(DatabaseManager::instance().database()); query.prepare(R"( UPDATE applications - SET full_name = :full_name, phone = :phone + SET full_name = :full_name, phone = :phone, email = :email WHERE id = :id )"); query.bindValue(":id", appId); query.bindValue(":full_name", fullName); query.bindValue(":phone", phone); + query.bindValue(":email", email); if (!query.exec()) { qWarning() << "Ошибка при обновлении профиля приложения:" << query.lastError().text(); diff --git a/database/dao/ApplicationDAO.h b/database/dao/ApplicationDAO.h index 506bcd5..dc9677f 100644 --- a/database/dao/ApplicationDAO.h +++ b/database/dao/ApplicationDAO.h @@ -24,7 +24,8 @@ public: [[nodiscard]] static bool updateProfileInfo( const int &appId, const QString &fullName, - const QString &phone + const QString &phone, + const QString &email = "" ); [[nodiscard]] static bool updateBankProfileId(int appId, const QString &bankProfileId); diff --git a/images/black_icon.png b/images/black_icon.png new file mode 100644 index 0000000..8a54f6f Binary files /dev/null and b/images/black_icon.png differ diff --git a/models/android/xml/Node.h b/models/android/xml/Node.h index bd2fca0..2545b40 100644 --- a/models/android/xml/Node.h +++ b/models/android/xml/Node.h @@ -45,6 +45,7 @@ public: QString hint; bool clickable = false; bool focusable = false; + bool password = false; private: int m_x1 = -1, m_y1 = -1, m_x2 = -1, m_y2 = -1; diff --git a/models/db/ApplicationInfo.h b/models/db/ApplicationInfo.h index 116f754..ff2f103 100644 --- a/models/db/ApplicationInfo.h +++ b/models/db/ApplicationInfo.h @@ -16,6 +16,7 @@ struct ApplicationInfo { QString status; QString comment; QString phone; + QString email; QString fullName; QString bankProfileId; QString currency; diff --git a/views/bank/AccountSettingsWindow.cpp b/views/bank/AccountSettingsWindow.cpp index 035d51e..ee0d8a4 100644 --- a/views/bank/AccountSettingsWindow.cpp +++ b/views/bank/AccountSettingsWindow.cpp @@ -17,6 +17,9 @@ #include "ozon/GetCardInfoScript.h" #include "ozon/GetLastTransactionsScript.h" #include "toss/LoginAndCheckAccountsTossScript.h" +#include "black/LoginAndCheckAccountsScript.h" +#include "black/GetProfileInfoScript.h" +#include "black/GetCardInfoScript.h" #include "dao/AccountDAO.h" #include "dao/ApplicationDAO.h" #include "bank/TransactionWindow.h" @@ -238,6 +241,80 @@ AccountSettingsWindow::AccountSettingsWindow( layout->addLayout(ozonRow); } + if (m_applicationInfo.code == "black" && m_applicationInfo.pinCodeChecked) { + QHBoxLayout *blackRow = new QHBoxLayout(); + + QPushButton *profileButton = new QPushButton("Обновить профиль"); + profileButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + profileButton->setStyleSheet("background-color: #1565C0; color: white;"); + QObject::connect(profileButton, &QPushButton::clicked, [this]() { + QMessageBox msgBox(this); + msgBox.setWindowTitle(tr("Обновить профиль?")); + msgBox.setText( + "Запустится скрипт получения профиля из " + m_applicationInfo.name + + ". Окно с настройками закроется."); + QPushButton *runBtn = msgBox.addButton(tr("Запустить"), QMessageBox::AcceptRole); + QPushButton *cancelBtn = msgBox.addButton(tr("Отмена"), QMessageBox::RejectRole); + msgBox.setDefaultButton(cancelBtn); + msgBox.setModal(true); + msgBox.exec(); + if (msgBox.clickedButton() == runBtn) { + startGetProfileInfo(m_applicationInfo.code); + close(); + } + }); + + QPushButton *cardButton = new QPushButton("Проверить аккаунты"); + cardButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + cardButton->setStyleSheet("background-color: #1565C0; color: white;"); + QObject::connect(cardButton, &QPushButton::clicked, [this]() { + QMessageBox msgBox(this); + msgBox.setWindowTitle(tr("Проверить аккаунты?")); + msgBox.setText( + "Запустится скрипт получения данных карты из " + m_applicationInfo.name + + ". Окно с настройками закроется."); + QPushButton *runBtn = msgBox.addButton(tr("Запустить"), QMessageBox::AcceptRole); + QPushButton *cancelBtn = msgBox.addButton(tr("Отмена"), QMessageBox::RejectRole); + msgBox.setDefaultButton(cancelBtn); + msgBox.setModal(true); + msgBox.exec(); + if (msgBox.clickedButton() == runBtn) { + startGetCardInfo(m_applicationInfo.code); + close(); + } + }); + + QPushButton *transactionsButton = new QPushButton("Проверить транзакции"); + transactionsButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); + transactionsButton->setStyleSheet("background-color: #1565C0; color: white;"); + QObject::connect(transactionsButton, &QPushButton::clicked, [this]() { + QMessageBox msgBox(this); + msgBox.setWindowTitle(tr("Проверить транзакции?")); + msgBox.setText( + "Запустится скрипт получения последних транзакций из " + m_applicationInfo.name + + ". Окно с настройками закроется."); + QPushButton *runBtn = msgBox.addButton(tr("За 24 часа"), QMessageBox::AcceptRole); + QPushButton *last5Btn = msgBox.addButton(tr("Последние 5"), QMessageBox::AcceptRole); + QPushButton *cancelBtn = msgBox.addButton(tr("Отмена"), QMessageBox::RejectRole); + msgBox.setDefaultButton(cancelBtn); + msgBox.setModal(true); + msgBox.exec(); + if (msgBox.clickedButton() == runBtn) { + startGetLastTransactions(m_applicationInfo.code); + close(); + } else if (msgBox.clickedButton() == last5Btn) { + startGetLastTransactions(m_applicationInfo.code, 5); + close(); + } + }); + + blackRow->addWidget(profileButton); + blackRow->addWidget(cardButton); + blackRow->addWidget(transactionsButton); + blackRow->addStretch(); + layout->addLayout(blackRow); + } + layout->addWidget(new QLabel("Счета")); QTableWidget *tableWidget = new QTableWidget(this); tableWidget->setSelectionMode(QAbstractItemView::NoSelection); @@ -315,6 +392,13 @@ void AccountSettingsWindow::startCheckPinCode(const QString &appCode) { connect(worker, &Ozon::LoginAndCheckAccountsScript::finished, thread, &QThread::quit); connect(worker, &Ozon::LoginAndCheckAccountsScript::finished, worker, &QObject::deleteLater); connect(thread, &QThread::finished, thread, &QObject::deleteLater); + } else if (appCode == "black") { + auto *worker = new Black::LoginAndCheckAccountsScript(m_deviceId, appCode, nullptr); + worker->moveToThread(thread); + connect(thread, &QThread::started, worker, &Black::LoginAndCheckAccountsScript::start); + connect(worker, &Black::LoginAndCheckAccountsScript::finished, thread, &QThread::quit); + connect(worker, &Black::LoginAndCheckAccountsScript::finished, worker, &QObject::deleteLater); + connect(thread, &QThread::finished, thread, &QObject::deleteLater); } else { thread->deleteLater(); return; @@ -324,29 +408,50 @@ void AccountSettingsWindow::startCheckPinCode(const QString &appCode) { } void AccountSettingsWindow::startGetProfileInfo(const QString &appCode) { - if (appCode != "ozon") return; - QThread *thread = new QThread(nullptr); - auto *worker = new Ozon::GetProfileInfoScript(m_deviceId, appCode, nullptr); - worker->moveToThread(thread); - connect(thread, &QThread::started, worker, &Ozon::GetProfileInfoScript::start); - connect(worker, &Ozon::GetProfileInfoScript::finished, thread, &QThread::quit); - connect(worker, &Ozon::GetProfileInfoScript::finished, worker, &QObject::deleteLater); - connect(thread, &QThread::finished, thread, &QObject::deleteLater); + + if (appCode == "ozon") { + auto *worker = new Ozon::GetProfileInfoScript(m_deviceId, appCode, nullptr); + worker->moveToThread(thread); + connect(thread, &QThread::started, worker, &Ozon::GetProfileInfoScript::start); + connect(worker, &Ozon::GetProfileInfoScript::finished, thread, &QThread::quit); + connect(worker, &Ozon::GetProfileInfoScript::finished, worker, &QObject::deleteLater); + connect(thread, &QThread::finished, thread, &QObject::deleteLater); + } else if (appCode == "black") { + auto *worker = new Black::GetProfileInfoScript(m_deviceId, appCode, nullptr); + worker->moveToThread(thread); + connect(thread, &QThread::started, worker, &Black::GetProfileInfoScript::start); + connect(worker, &Black::GetProfileInfoScript::finished, thread, &QThread::quit); + connect(worker, &Black::GetProfileInfoScript::finished, worker, &QObject::deleteLater); + connect(thread, &QThread::finished, thread, &QObject::deleteLater); + } else { + thread->deleteLater(); + return; + } + thread->start(); } void AccountSettingsWindow::startGetCardInfo(const QString &appCode) { - if (appCode != "ozon") return; - - QThread *thread = new QThread(nullptr); - auto *worker = new Ozon::GetCardInfoScript(m_deviceId, appCode, "", nullptr); - worker->moveToThread(thread); - connect(thread, &QThread::started, worker, &Ozon::GetCardInfoScript::start); - connect(worker, &Ozon::GetCardInfoScript::finished, thread, &QThread::quit); - connect(worker, &Ozon::GetCardInfoScript::finished, worker, &QObject::deleteLater); - connect(thread, &QThread::finished, thread, &QObject::deleteLater); - thread->start(); + if (appCode == "ozon") { + QThread *thread = new QThread(nullptr); + auto *worker = new Ozon::GetCardInfoScript(m_deviceId, appCode, "", nullptr); + worker->moveToThread(thread); + connect(thread, &QThread::started, worker, &Ozon::GetCardInfoScript::start); + connect(worker, &Ozon::GetCardInfoScript::finished, thread, &QThread::quit); + connect(worker, &Ozon::GetCardInfoScript::finished, worker, &QObject::deleteLater); + connect(thread, &QThread::finished, thread, &QObject::deleteLater); + thread->start(); + } else if (appCode == "black") { + QThread *thread = new QThread(nullptr); + auto *worker = new Black::GetCardInfoScript(m_deviceId, appCode, nullptr); + worker->moveToThread(thread); + connect(thread, &QThread::started, worker, &Black::GetCardInfoScript::start); + connect(worker, &Black::GetCardInfoScript::finished, thread, &QThread::quit); + connect(worker, &Black::GetCardInfoScript::finished, worker, &QObject::deleteLater); + connect(thread, &QThread::finished, thread, &QObject::deleteLater); + thread->start(); + } } void AccountSettingsWindow::startGetLastTransactions(const QString &appCode, int maxCount) { diff --git a/views/bank/BankSettingsWindow.cpp b/views/bank/BankSettingsWindow.cpp index 4b1eb90..2af39b4 100644 --- a/views/bank/BankSettingsWindow.cpp +++ b/views/bank/BankSettingsWindow.cpp @@ -26,6 +26,7 @@ #include "toss/LoginAndCheckAccountsTossScript.h" #include "widget/common/CollapsibleSection.h" #include "ziraat/LoginAndCheckAccountsZiraatScript.h" +#include "black/LoginAndCheckAccountsScript.h" QTableWidgetItem *BankSettingsWindow::createTableWidget(const QString &text) { QTableWidgetItem *item = new QTableWidgetItem(text); @@ -87,6 +88,13 @@ void BankSettingsWindow::startCheckPinCode(const QString &appCode) { connect(worker, &LoginAndCheckAccountsTossScript::finished, thread, &QThread::quit); connect(worker, &LoginAndCheckAccountsTossScript::finished, worker, &QObject::deleteLater); connect(thread, &QThread::finished, thread, &QObject::deleteLater); + } else if (appCode == "black") { + auto *worker = new Black::LoginAndCheckAccountsScript(m_deviceId, appCode, nullptr); + worker->moveToThread(thread); + connect(thread, &QThread::started, worker, &Black::LoginAndCheckAccountsScript::start); + connect(worker, &Black::LoginAndCheckAccountsScript::finished, thread, &QThread::quit); + connect(worker, &Black::LoginAndCheckAccountsScript::finished, worker, &QObject::deleteLater); + connect(thread, &QThread::finished, thread, &QObject::deleteLater); } else { thread->deleteLater(); return;