diff --git a/android/birbank/CommonBirbankScript.cpp b/android/birbank/CommonBirbankScript.cpp new file mode 100644 index 0000000..a8a7df9 --- /dev/null +++ b/android/birbank/CommonBirbankScript.cpp @@ -0,0 +1,216 @@ +#include "CommonBirbankScript.h" + +#include +#include +#include +#include + +#include "adb/AdbUtils.h" +#include "net/NetworkService.h" + +CommonBirbankScript::CommonBirbankScript(QObject *parent) + : QObject(parent) { +} + +CommonBirbankScript::~CommonBirbankScript() = default; + +void CommonBirbankScript::start() { + doStart(); + emit finished(); +} + +Node CommonBirbankScript::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 CommonBirbankScript::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() << "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() << "Process has not started"; + return false; + } + + // Даем минимальное время на запуск + QThread::msleep(2500); + int counter = 0; + bool findAndInputPincode = false; + + while (true) { + counter++; + + // Если более 10 раз мы пытаемся перейти на домашнюю + if (counter > 10) { + AdbUtils::takeScreenshot(deviceId, "too_many_attempts"); + return false; + } + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); + + Node closeBannerNode = xmlScreenParser.findButtonNode("Maraqlı deyil", false); + if (!closeBannerNode.isEmpty()) { + AdbUtils::makeTap(deviceId, closeBannerNode.x(), closeBannerNode.y()); + QThread::msleep(1000); + continue; + } + + if (xmlScreenParser.isHomeScreen()) { + return true; + } + + // Проверяем, что это экран ввода пин-кода + if (!findAndInputPincode) { + if (const QList pincode = xmlScreenParser.findPinCodeNodes(pinCode); pincode.size() == 4) { + for (const Node &node: pincode) { + AdbUtils::makeTap(deviceId, node.x(), node.y()); + QThread::msleep(QRandomGenerator::global()->bounded(200, 901)); + } + // После ввода пин-код нужно сделать задержку, пока подгрузка идет, чтобы снова не спарсить + findAndInputPincode = true; + QThread::msleep(3000); + continue; + } + } + + QThread::msleep(1000); + } +} + +bool CommonBirbankScript::goToAllProducts( + const QString &deviceId, + const int width, + const int height +) { + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, m_counter)); + Node closeBannerNode = xmlScreenParser.findButtonNode("Maraqlı deyil", false); + if (!closeBannerNode.isEmpty()) { + AdbUtils::makeTap(deviceId, closeBannerNode.x(), closeBannerNode.y()); + QThread::msleep(500); + } + + if (xmlScreenParser.isHomeScreen()) { + Node allMyAccountBtn = xmlScreenParser.findButtonNode("All My Accounts", false); + if (!allMyAccountBtn.isEmpty()) { + AdbUtils::makeTap(deviceId, allMyAccountBtn.x(), allMyAccountBtn.y()); + QThread::msleep(500); + return true; + } + } + return false; +} + +void CommonBirbankScript::postAccountsData(const QList &accounts) { + if (accounts.isEmpty()) { + return; + } + + auto *thread = new QThread; + auto *worker = new NetworkService; + + QJsonArray list; + for (const AccountInfo &account: accounts) { + QJsonObject obj; + obj["appName"] = account.appCode; + obj["lastNumbers"] = account.lastNumbers; + obj["amount"] = account.amount; + obj["currency"] = account.currency; + obj["description"] = account.description; + obj["status"] = accountStatusToString(account.status); + + list.append(obj); + } + // FIXME доставить десктоп ид из настроеек + worker->setDeviceId(accounts.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 CommonBirbankScript::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; + + // FIXME доставить десктоп ид из настроеек + 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 CommonBirbankScript::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; + + // FIXME доставить десктоп ид из настроеек + 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 CommonBirbankScript::stop() { + m_running = false; +} diff --git a/android/birbank/CommonBirbankScript.h b/android/birbank/CommonBirbankScript.h new file mode 100644 index 0000000..37b2ecb --- /dev/null +++ b/android/birbank/CommonBirbankScript.h @@ -0,0 +1,68 @@ +#pragma once + +#include +#include "ScreenXmlBirbankParser.h" +#include "android/xml/Node.h" +#include "db/AccountInfo.h" +#include "db/TransactionInfo.h" + +class CommonBirbankScript : public QObject { + Q_OBJECT + +public: + explicit CommonBirbankScript(QObject *parent = nullptr); + + ~CommonBirbankScript() override; + +protected: + virtual void doStart() = 0; + + bool goToAllProducts( + const QString &deviceId, + 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 + ); + + Node swipeToButton( + const QString &deviceId, + const QString &text, + int &counter, int width, int height + ); + + bool runAppAndGoToHomeScreen( + const QString &deviceId, + const QString &packageName, + const QString &pinCode, + int width, + int height + ); + + bool m_running = true; + ScreenXmlBirbankParser 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(CommonBirbankScript); +}; diff --git a/android/birbank/GetLastDaysHistoryBirbankScript.cpp b/android/birbank/GetLastDaysHistoryBirbankScript.cpp new file mode 100644 index 0000000..5966518 --- /dev/null +++ b/android/birbank/GetLastDaysHistoryBirbankScript.cpp @@ -0,0 +1,164 @@ +#include "GetLastDaysHistoryBirbankScript.h" + +#include +#include +#include +#include + +#include "DatabaseManager.h" +#include "adb/AdbUtils.h" +#include "dao/AccountDAO.h" +#include "dao/ApplicationDAO.h" +#include "dao/DeviceDAO.h" +#include "dao/TransactionDAO.h" +#include "db/DeviceInfo.h" +#include "net/NetworkService.h" +#include "time/DateUtils.h" + +GetLastDaysHistoryBirbankScript::GetLastDaysHistoryBirbankScript( + AccountInfo account, + QObject *parent +) : CommonBirbankScript(parent), + m_account(std::move(account)) { +} + +GetLastDaysHistoryBirbankScript::~GetLastDaysHistoryBirbankScript() = default; + +bool GetLastDaysHistoryBirbankScript::saveAndPostNewTransactionData( + const AccountInfo &account, + const TransactionInfo &transaction, + const TransactionStatus &status +) { + TransactionInfo tr; + tr.accountId = account.id; + tr.description = transaction.description; + tr.bankTime = transaction.bankTime; + tr.bankName = transaction.bankName; + tr.amount = transaction.amount; + tr.phone = transaction.phone; + tr.updateTime = DateUtils::getUtcNow(); + tr.fee = transaction.fee; + tr.name = transaction.name; + tr.bankTrExternalId = transaction.bankTrExternalId; + tr.status = status; + tr.type = TransactionType::Card; + tr.completeTime = transaction.completeTime; + tr.id = TransactionDAO::insertTransaction(tr); + + if (tr.id == -1) { + return false; + } + postNewTransactionData(account, tr); + return true; +} + +bool GetLastDaysHistoryBirbankScript::updateAndPostTransactionData( + const AccountInfo &account, + const int transactionId, + const TransactionStatus status, + const QString &oldDesc, + const QString &newDesc +) { + if (TransactionDAO::updateTransaction(transactionId, status, oldDesc, newDesc)) { + updateTransactionData(account, transactionId, status, oldDesc, newDesc); + return true; + } + return false; +} + +void GetLastDaysHistoryBirbankScript::doStart() { + // берем все транзакции из БД + const DeviceInfo device = DeviceDAO::getDeviceById(m_account.deviceId); + const ApplicationInfo app = ApplicationDAO::getApplication(m_account.deviceId, m_account.appCode); + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_account.deviceId, ++m_counter)); + if (!xmlScreenParser.isHomeScreen()) { + if (!runAppAndGoToHomeScreen(device.id, app.package, app.pinCode, device.screenWidth, device.screenHeight)) { + m_error = "Не смогли дойти до домашней страницы"; + qCritical() << m_error; + emit finishedWithResult(m_error); + return; + } + } + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_account.deviceId, ++m_counter)); + + Node closeBannerNode = xmlScreenParser.findButtonNode("Maraqlı deyil", false); + if (!closeBannerNode.isEmpty()) { + AdbUtils::makeTap(device.id, closeBannerNode.x(), closeBannerNode.y()); + QThread::msleep(1000); + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_account.deviceId, ++m_counter)); + } + + if (xmlScreenParser.isHomeScreen()) { + // QList localTransactions = TransactionDAO::getTransactionsWithinHours(m_account.id, 48); + + QList oldAccounts = AccountDAO::getAccounts(m_account.deviceId, m_account.appCode); + QMap accountMap; + for (AccountInfo &oldAccount: oldAccounts) { + if (oldAccount.status == AccountStatus::Active) { + accountMap.insert(oldAccount.lastNumbers, oldAccount); + } + } + QList accounts = xmlScreenParser.parseAccountsInfo(); + for (auto account: accounts) { + if (accountMap.contains(account.lastNumbers)) { + account.deviceId = m_account.deviceId; + account.appCode = m_account.appCode; + account.currency = accountMap[account.lastNumbers].currency; + account.description = accountMap[account.lastNumbers].description; + account.status = AccountStatus::Active; + if (!AccountDAO::upsertAccount(account)) { + m_error = "Not updated: " + account.lastNumbers; + qCritical() << m_error; + } + } + } + + QList savedAccounts = AccountDAO::getAccounts(m_account.deviceId, m_account.appCode); + postAccountsData(savedAccounts); + + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_account.deviceId, ++m_counter)); + Node cardText = xmlScreenParser.findTextNode(m_account.lastNumbers); + if (!cardText.isEmpty()) { + AdbUtils::makeTap(device.id, cardText.x(), cardText.y()); + QThread::msleep(2000); + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_account.deviceId, ++m_counter)); + Node cardTitleText = xmlScreenParser.findTextNode(m_account.lastNumbers); + Node cardHistory = xmlScreenParser.findTextNode("История платежей"); + if (!cardTitleText.isEmpty() && !cardHistory.isEmpty()) { + QList transactions = xmlScreenParser.parsePositiveTransactions(); + QList savedTransactions = TransactionDAO::getTransactionsWithinHours(m_account.id, 48); + for (TransactionInfo &transaction: transactions) { + bool isSaved = false; + for (TransactionInfo &savedTransaction: savedTransactions) { + if (savedTransaction.amount == transaction.amount + && savedTransaction.bankTime == transaction.bankTime) { + isSaved = true; + break; + } + } + if (!isSaved) { + if (!saveAndPostNewTransactionData(m_account, transaction, TransactionStatus::Complete)) { + qDebug() << "--- saveAndPostNewTransactionData failed"; + } + } + } + } else { + m_error = "Card page not found: " + m_account.deviceId + " " + m_account.lastNumbers; + qWarning() << m_error; + emit finishedWithResult(m_error); + return; + } + } else { + m_error = "Card not found: " + m_account.deviceId + " " + m_account.lastNumbers; + qWarning() << m_error; + emit finishedWithResult(m_error); + return; + } + } + AdbUtils::goBack(m_account.deviceId); + emit finishedWithResult(m_error); +} diff --git a/android/birbank/GetLastDaysHistoryBirbankScript.h b/android/birbank/GetLastDaysHistoryBirbankScript.h new file mode 100644 index 0000000..1895378 --- /dev/null +++ b/android/birbank/GetLastDaysHistoryBirbankScript.h @@ -0,0 +1,35 @@ +#pragma once +#include "CommonBirbankScript.h" + +class GetLastDaysHistoryBirbankScript final : public CommonBirbankScript { + Q_OBJECT + +public: + ~GetLastDaysHistoryBirbankScript() override; + + explicit GetLastDaysHistoryBirbankScript( + AccountInfo account, + QObject *parent = nullptr + ); + +protected: + void doStart() override; + +private: + const AccountInfo m_account; + QString m_error; + + bool saveAndPostNewTransactionData( + const AccountInfo &account, + const TransactionInfo &transaction, + const TransactionStatus &status + ); + + bool updateAndPostTransactionData( + const AccountInfo &account, + int transactionId, + TransactionStatus status, + const QString &oldDesc, + const QString &newDesc + ); +}; diff --git a/android/birbank/LoginAndCheckAccountsBirbankScript.cpp b/android/birbank/LoginAndCheckAccountsBirbankScript.cpp new file mode 100644 index 0000000..d09b31d --- /dev/null +++ b/android/birbank/LoginAndCheckAccountsBirbankScript.cpp @@ -0,0 +1,150 @@ +#include "LoginAndCheckAccountsBirbankScript.h" + +#include + +#include "adb/AdbUtils.h" +#include "dao/AccountDAO.h" +#include "dao/ApplicationDAO.h" +#include "dao/DeviceDAO.h" +#include "dao/TransactionDAO.h" +#include "time/DateUtils.h" + +LoginAndCheckAccountsBirbankScript::LoginAndCheckAccountsBirbankScript( + QString deviceId, + QString appCode, + QObject *parent +) : CommonBirbankScript(parent), + m_deviceId(std::move(deviceId)), m_appCode(std::move(appCode)) { +} + +LoginAndCheckAccountsBirbankScript::~LoginAndCheckAccountsBirbankScript() = default; + + +void LoginAndCheckAccountsBirbankScript::doStart() { + const DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId); + const ApplicationInfo app = ApplicationDAO::getApplication(m_deviceId, m_appCode); + + + if (false) { + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); + Node cardText = xmlScreenParser.findTextNode("2480"); + if (!cardText.isEmpty()) { + AdbUtils::makeTap(device.id, cardText.x(), cardText.y()); + QThread::msleep(2000); + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); + Node cardTitleText = xmlScreenParser.findTextNode("2480"); + Node cardHistory = xmlScreenParser.findTextNode("История платежей"); + if (!cardTitleText.isEmpty() && !cardHistory.isEmpty()) { + QList transactions = xmlScreenParser.parsePositiveTransactions(); + + QList savedTransactions = TransactionDAO::getTransactions(89); + for (TransactionInfo &transaction: transactions) { + bool isSaved = false; + for (TransactionInfo &savedTransaction: savedTransactions) { + if (savedTransaction.amount == transaction.amount + && savedTransaction.bankTime == transaction.bankTime) { + isSaved = true; + break; + } + } + if (!isSaved) { + TransactionInfo tr; + tr.accountId = 89; + tr.description = transaction.description; + tr.bankTime = transaction.bankTime; + tr.bankName = transaction.bankName; + tr.amount = transaction.amount; + tr.phone = transaction.phone; + tr.updateTime = DateUtils::getUtcNow(); + tr.fee = transaction.fee; + tr.name = transaction.name; + tr.bankTrExternalId = transaction.bankTrExternalId; + tr.status = TransactionStatus::Complete; + tr.type = TransactionType::Card; + tr.completeTime = transaction.completeTime; + tr.id = TransactionDAO::insertTransaction(tr); + + if (tr.id == -1) { + qDebug() << "--- saveAndPostNewTransactionData failed"; + } + AccountInfo account = AccountDAO::getAccountById(89); + postNewTransactionData(account, tr); + + // if (!saveAndPostNewTransactionData(m_account, transaction, TransactionStatus::Complete)) { + // qDebug() << "--- saveAndPostNewTransactionData failed"; + // } + } + } + } else { + m_error = "Card page not found: " + m_deviceId + " " + m_appCode; + qWarning() << m_error; + emit finishedWithResult(m_error); + return; + } + } else { + m_error = "Card not found: " + m_deviceId + " " + m_appCode; + qWarning() << m_error; + emit finishedWithResult(m_error); + return; + } + + } else { + // "Maraqlı deyil" - не интересно + 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; + 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; + } else { + if (!ApplicationDAO::updatePinCodeStatus(app.id, "yes", "")) { + qWarning() << "Cant update the pincode: " << m_deviceId << " " << m_appCode; + } + + QThread::msleep(2000); + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); + QList oldAccounts = AccountDAO::getAccounts(m_deviceId, m_appCode); + QMap accountMap; + for (AccountInfo &oldAccount: oldAccounts) { + if (oldAccount.status == AccountStatus::Active) { + accountMap.insert(oldAccount.lastNumbers, oldAccount); + } + } + + QList accounts = xmlScreenParser.parseAccountsInfo(); + for (auto account: accounts) { + if (accountMap.contains(account.lastNumbers)) { + account.deviceId = m_deviceId; + account.appCode = m_appCode; + account.currency = accountMap[account.lastNumbers].currency; + account.description = accountMap[account.lastNumbers].description; + account.status = AccountStatus::Active; + if (!AccountDAO::upsertAccount(account)) { + m_error = "Not updated: " + account.lastNumbers; + qCritical() << m_error; + } + } + } + + QList savedAccounts = AccountDAO::getAccounts(m_deviceId, m_appCode); + postAccountsData(savedAccounts); + } + + AdbUtils::tryToKillApplication(m_deviceId, app.package); + } + emit finishedWithResult(m_error); +} diff --git a/android/birbank/LoginAndCheckAccountsBirbankScript.h b/android/birbank/LoginAndCheckAccountsBirbankScript.h new file mode 100644 index 0000000..fd3581d --- /dev/null +++ b/android/birbank/LoginAndCheckAccountsBirbankScript.h @@ -0,0 +1,23 @@ +#pragma once +#include "CommonBirbankScript.h" + +class LoginAndCheckAccountsBirbankScript final : public CommonBirbankScript { + Q_OBJECT + +public: + ~LoginAndCheckAccountsBirbankScript() override; + + explicit LoginAndCheckAccountsBirbankScript( + QString deviceId, + QString appCode, + QObject *parent = nullptr + ); + +protected: + void doStart() override; + +private: + const QString m_deviceId; + const QString m_appCode; + QString m_error; +}; diff --git a/android/birbank/ScreenXmlBirbankParser.cpp b/android/birbank/ScreenXmlBirbankParser.cpp new file mode 100644 index 0000000..6e8e11c --- /dev/null +++ b/android/birbank/ScreenXmlBirbankParser.cpp @@ -0,0 +1,732 @@ +#include "ScreenXmlBirbankParser.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "adb/AdbUtils.h" +#include "android/xml/Node.h" +#include "db/AccountInfo.h" +#include "db/TransactionInfo.h" +#include "time/DateUtils.h" + +ScreenXmlBirbankParser::ScreenXmlBirbankParser(QObject *parent) : QObject(parent) { +} + +ScreenXmlBirbankParser::~ScreenXmlBirbankParser() = default; + +static const QRegularExpression boundsRegex(R"(\[([0-9]+),([0-9]+)\]\[([0-9]+),([0-9]+)\])"); +static const QRegularExpression onlyDigitsRegex(R"([^0-9])"); +static const QRegularExpression onlyDigitsAndDotRegex(R"([^0-9.])"); + +static const QRegularExpression rePhone(R"raw((\+7\(?\d{3}\)?[- ]?\d{3}[- ]?\d{2}[- ]?\d{2}))raw"); +static const QRegularExpression reFromBank(R"raw(из (.+?) на)raw"); +static const QRegularExpression reToBank(R"raw(в (.+?) через)raw"); +static const QRegularExpression reSbpId(R"(СБП(?: С2С)? ([A-Z0-9]{30,}))"); +static const QRegularExpression reIdAlt(R"(ID перевода (\d+))"); +static const QRegularExpression reDate(R"((?:Дата операции|дата) (\d{2}\.\d{2}\.\d{4}(?: \d{2}:\d{2}:\d{2})?))"); +static const QRegularExpression reSender(R"(от ([^\(]+))"); +static const QRegularExpression reNameAlt(R"(Перевод ([А-Яа-яЁё\s\*]+) в)"); + +static const QRegularExpression reCardData(R"(цифрами\s+(\d\s\d\s\d\s\d).*?Сумма:\s([\d\s]+)\sруб)"); + +Node ScreenXmlBirbankParser::tryToFindBannerOrDialog(const int width, const int height) { + // 1. Ищем push banner - он перекрывает весь экран + Node first = m_nodes[0]; + for (const Node &node: m_nodes) { + // Если это однозначно, что пуш-баннер + if (node.resourceId == "aft-popup-pushNotification-iconClose" && node.className == "android.widget.Button") { + return node; + } + // Если есть кнопка "Позже" + if (node.text.trimmed() == "Позже" && node.className == "android.widget.Button") { + return node; + } + // Если нашли пустую кнпоку + if (node.className == "android.widget.Button" && node.clickable + && node.text.isEmpty() && node.contentDesc.isEmpty()) { + // + if (node.x() > width / 2 && node.y() < width / 2) { + return node; + } + } + } + return {}; +} + +Node ScreenXmlBirbankParser::findAccountButton(const QString &numbers) { + // TODO возможно это не так, надо уточнять + return findTextNode(QString("LEVENT - %1").arg(numbers)); +} + +/** + * Смотрим что есть текс "Мы заботимся о вашей безопасности" и + * кнопка "Отложить" + * @return координаты кнопки для закрытия + */ +Node ScreenXmlBirbankParser::tryToFindSecurityBanner() { + Node text; + Node closeBtn; + for (const Node &node: m_nodes) { + if (node.text.trimmed() == "Мы заботимся о вашей безопасности" && node.className == "android.app.Dialog") { + text = node; + } + if (node.text.trimmed() == "Отложить" && node.className == "android.widget.Button") { + closeBtn = node; + } + } + + // Не нужно проверять все, если хотя бы одна координата есть + if (!text.isEmpty() && !closeBtn.isEmpty()) { + return closeBtn; + } + return {}; +} + +Node ScreenXmlBirbankParser::tryToFindSelectBottomSheet() { + Node closeBtn; + for (const Node &node: m_nodes) { + if ((node.text.trimmed() == "Отмена" || node.text.trimmed() == "Отложить") && node.className == + "android.widget.Button") { + closeBtn = node; + } + } + // Не нужно проверять все, если хотя бы одна координата есть + if (!closeBtn.isEmpty()) { + return closeBtn; + } + return {}; +} + +Node ScreenXmlBirbankParser::findTextNode(const QString &text) { + for (const Node &node: m_nodes) { + if (node.className == "android.widget.TextView" && node.text.trimmed() == text) { + return node; + } + } + return {}; +} + +Node ScreenXmlBirbankParser::findTextNode(const QString &text, const int x1, const int y1, const int x2, const int y2) { + for (const Node &node: m_nodes) { + if (node.className == "android.widget.TextView" && node.text.trimmed() == text + && node.x() >= x1 && node.x() <= x2 && node.y() >= y1 && node.y() <= y2) { + return node; + } + } + return {}; +} + +Node ScreenXmlBirbankParser::findEditText(const QString &text) { + for (const Node &node: m_nodes) { + if (node.className == "android.widget.EditText" && node.text == text) { + return node; + } + } + return {}; +} + +Node ScreenXmlBirbankParser::findEditTextByResourceId(const QString &resourceId) { + for (const Node &node: m_nodes) { + if (node.className == "android.widget.EditText" && node.resourceId == resourceId) { + return node; + } + } + return {}; +} + +Node ScreenXmlBirbankParser::findButtonNode(const QString &text, const bool contains) { + for (const Node &node: m_nodes) { + if (contains) { + if (node.className == "android.widget.Button" && node.text.contains(text)) { + return node; + } + } else { + if (node.className == "android.widget.Button" && node.text.trimmed() == text) { + return node; + } + } + } + return {}; +} + +bool ScreenXmlBirbankParser::isHomeScreen() { + Node activitiesBtn; + for (const Node &node: m_nodes) { + if (node.text.trimmed() == "Карты и счета") { + activitiesBtn = node; + } + } + return !activitiesBtn.isEmpty(); +} + +QList ScreenXmlBirbankParser::findPinCodeNodes(const QString &pinCode) { + QMap buttons; + const QSet numberSet = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}; + + for (const Node &node: m_nodes) { + const QString &text = node.text.trimmed(); + if (node.className == "android.widget.TextView" && numberSet.contains(text)) { + buttons[text] = node; + } + } + + QList nodes; + for (int i = 0; i < pinCode.length(); ++i) { + nodes.append(buttons[QString(pinCode.at(i))]); + } + return nodes; +} + +QList ScreenXmlBirbankParser::getLast2DaysTransactions() { + QList nodes; + for (const Node &node: m_nodes) { + if (node.className == "android.widget.Button" && node.text.contains("рубл") + && (node.text.contains("Минус") || node.text.contains("Плюс"))) { + nodes.append(node); + } + } + return nodes; +} + +/** +* + */ +void ScreenXmlBirbankParser::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() == "node") { + 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(); + } + + m_nodes.append(node); + } + } +} + +QDomElement findSubElementBirbank(const QDomElement &elem, const QString &index) { + if (elem.hasChildNodes()) { + QDomNodeList subInfoList = elem.childNodes(); + for (QDomNode subInfoNode: subInfoList) { + if (!subInfoNode.isElement()) continue; + QDomElement subInfo = subInfoNode.toElement(); + if (subInfo.attribute("index") == index) { + return subInfo; + } + } + } + return {}; +} + +QString findSubInfoBirbank(const QDomElement &elem, const QString &index) { + QDomElement subInfo = findSubElementBirbank(elem, index); + if (!subInfo.isNull()) { + return subInfo.attribute("text"); + } + return ""; +} + +bool hasTimeComponentBirbank(const QString &datetimeStr) { + QDateTime dt = QDateTime::fromString(datetimeStr, "dd.MM.yyyy HH:mm:ss"); + if (!dt.isValid()) { + return false; // Строка не соответствует формату даты и времени + } + return dt.time() != QTime(0, 0, 0); // Проверка, что время не равно 00:00:00 +} + +QString transformBankDateTimeBirbank(const QString &date) { + QDateTime dt; + if (date.contains(",")) { + // 10.05.2025, 15:03 + dt = QDateTime::fromString(date, "dd.MM.yyyy, HH:mm"); + } else { + dt = QDateTime::fromString(date, "dd.MM.yyyy HH:mm:ss"); + } + if (!dt.isValid()) { + dt = QDateTime::fromString(date, "dd.MM.yyyy HH:mm"); + } + if (!dt.isValid()) { + return date; // не распарсили + } + return dt.toString("dd.MM.yyyy HH:mm:ss"); +} + +void parseTransferInfoBirbank(const QString &text, TransactionInfo &info) { + if (const auto m = reSender.match(text); m.hasMatch()) { + info.name = m.captured(1).trimmed(); + } else if (const auto m1 = reNameAlt.match(text); m1.hasMatch()) { + info.name = m1.captured(1).trimmed(); + } + + if (const auto m = rePhone.match(text); m.hasMatch()) { + info.phone = m.captured(1).trimmed().remove(onlyDigitsRegex); + } + + if (const auto m = reFromBank.match(text); m.hasMatch()) { + if (const QString bank = m.captured(1).trimmed(); !bank.isEmpty()) { + info.bankName = bank; + } + } else if (const auto m1 = reToBank.match(text); m1.hasMatch()) { + if (const QString bank = m1.captured(1).trimmed(); !bank.isEmpty()) { + info.bankName = bank; + } + } + + if (const auto m = reSbpId.match(text); m.hasMatch()) { + info.bankTrExternalId = m.captured(1); + } else if (const auto m1 = reIdAlt.match(text); m1.hasMatch()) { + info.bankTrExternalId = m1.captured(1); + } + + if (const auto m = reDate.match(text); m.hasMatch()) { + const QString date = m.captured(1); + // если не пустая и дата у нас с 00:00 + // if (hasTimeComponent2(date)) { + info.bankTime = transformBankDateTimeBirbank(date); + // } + } +} + +void traverseNodesBirbank(const QDomNode &node) { + if (node.isElement()) { + QDomElement elem = node.toElement(); + + if (node.isElement()) { + QDomElement elem = node.toElement(); + + if (elem.tagName() == "node" && + elem.attribute("class") == "android.widget.Button" && + elem.attribute("text") == "Детали операции") { + // Все что ниже деталей операции + QDomNodeList detailInfo = elem.parentNode().childNodes(); + + + for (int j = 0; j < detailInfo.count(); ++j) { + QDomNode info = detailInfo.at(j); + if (!info.isElement()) continue; + QDomElement infoElement = info.toElement(); + QString index = infoElement.attribute("index"); + switch (index.toInt()) { + case 2: + qDebug() << "Дата операции:" << findSubInfoBirbank(infoElement, "1"); + break; + case 5: + qDebug() << "Название:" << findSubInfoBirbank(infoElement, "1"); + qDebug() << "-------------------------------------"; + // parseTransfer(findSubInfo2(infoElement)); + break; + case 7: + qDebug() << "Комиссия:" << findSubInfoBirbank(infoElement, "1"); + break; + default: + qDebug() << "Прочее:" << findSubInfoBirbank(infoElement, "1"); + break; + } + } + } + } + } + + // Рекурсивно обходим всех детей + QDomNode child = node.firstChild(); + while (!child.isNull()) { + traverseNodesBirbank(child); + child = child.nextSibling(); + } +} + +QDomNode findNodeBirbank(const QDomNode &node, const QString &text) { + if (node.isElement()) { + QString nodeText = node.toElement().attribute("text"); + if (nodeText.toLower().startsWith(text)) { + return node; + } + } + + + QDomNode child = node.firstChild(); + while (!child.isNull()) { + QDomNode result = findNodeBirbank(child, text); + if (!result.isNull()) { + return result; + } + child = child.nextSibling(); + } + return {}; +} + +float extractAmountsBirbank(const QString &text) { + // Разрешаем пробелы внутри чисел, захватываем "рубль", "рубля", "рублей" и т.д. + // QRegularExpression re(R"((Плюс|Минус)\s+([\d\s]+)\s+рубл\w*\s+(\d+)\s+копе\w*)"); + QRegularExpression re( + R"((Плюс|Минус)\s+([\d\s]+)\s+рубл\w*\s+(\d+)\s+копе\w*)", + QRegularExpression::UseUnicodePropertiesOption + ); + + QRegularExpressionMatch match = re.match(text); + if (match.hasMatch()) { + QString signStr = match.captured(1); // "Плюс" или "Минус" + QString rublesStr = match.captured(2).remove(' '); // "1 000" → "1000" + int rubles = rublesStr.toInt(); + int kopecks = match.captured(3).toInt(); + + int totalKopecks = rubles * 100 + kopecks; + if (signStr == "Минус") { + totalKopecks *= -1; + } + + return totalKopecks / 100.0f; + } + return 0; +} + +QString beforeAmountBirbank(const QString &text) { + int index = text.indexOf(QRegularExpression("Плюс|Минус")); + if (index != -1) { + return text.left(index).trimmed(); + } + return text; // если не найдено — вернуть всю строку +} + +double parseAmountBirbank(const QString &amountAsString) { + QString numberString = amountAsString; + numberString.remove(QRegularExpression(R"([^\dточка])")); + numberString.replace("точка", "."); + double amount = numberString.toDouble(); + + if (amountAsString.startsWith("-")) { + amount *= -1; + } + + return amount; +} + +QDomElement findElementWithPaymentsBirbank(const QDomElement &parent, const QString &resourceId) { + QDomElement element = parent.firstChildElement(); + while (!element.isNull()) { + QDomElement child = element.firstChildElement(); + while (!child.isNull()) { + if (child.hasAttribute("resource-id") && child.attribute("resource-id") == resourceId) { + return child; + } + child = child.nextSiblingElement(); + } + + QDomElement found = findElementWithPaymentsBirbank(element, resourceId); + if (!found.isNull()) { + return found; + } + element = element.nextSiblingElement(); + } + return {}; +} + +QDateTime getUtcFromBakuDate(const QString &date, const QString &time) { + int year = QDate::currentDate().year(); // Или укажи вручную нужный год + QString combined = QString("%1 %2 %3").arg(date).arg(year).arg(time); // "24 июля 2025 0:31" + + QLocale ruLocale(QLocale::Russian, QLocale::Russia); + QDateTime dt = ruLocale.toDateTime(combined, "d MMMM yyyy H:mm"); + + if (dt.isValid()) { + // Установка часового пояса Asia/Baku + dt.setTimeZone(QTimeZone("Asia/Baku")); + // Перевод в UTC + QDateTime utc = dt.toTimeZone(QTimeZone::utc()); + return utc; + } else { + qWarning() << "Ошибка разбора даты/времени"; + } + return {}; +} + + +QList ScreenXmlBirbankParser::parsePositiveTransactions() { + QList transactions; + + QDomElement child = findElementWithPaymentsBirbank(m_xml, "az.kapitalbank.mbanking:id/rv_pfm_operations"); + // QDomElement child = findElementWithPaymentsBirbank(m_xml, "az.kapitalbank.mbanking:id/rv_operations"); + QDomNodeList listView = child.childNodes(); + qDebug() << listView.count(); + if (!listView.isEmpty()) { + QString date = ""; + + for (QDomNode node: listView) { + const QDomNodeList trBoxGroup = node.childNodes(); + if (trBoxGroup.count() > 0) { + TransactionInfo transaction; + for (QDomNode item: trBoxGroup) { + QDomElement groupItem = item.toElement(); + if ("az.kapitalbank.mbanking:id/tv_date" == groupItem.attribute("resource-id")) { + date = groupItem.attribute("text"); + } else if ("az.kapitalbank.mbanking:id/transaction_title" == groupItem.attribute("resource-id")) { + transaction.description = groupItem.attribute("text"); + } else if ("az.kapitalbank.mbanking:id/transaction_time" == groupItem.attribute("resource-id")) { + transaction.bankTime = date + " " + groupItem.attribute("text"); + transaction.completeTime = getUtcFromBakuDate(date, groupItem.attribute("text")); + } else if ("az.kapitalbank.mbanking:id/transaction_amount" == groupItem.attribute("resource-id")) { + auto amount = groupItem.attribute("text"); + if (amount.startsWith("+")) { + transaction.amount = amount.remove("+").remove("₼").replace(",", ".").trimmed().toFloat(); + } + } + } + if (transaction.amount > 0) { + transactions.append(transaction); + qDebug().noquote().nospace() << convertTransactionToString(transaction); + } + } + } + } else { + qDebug() << "ListView is empty"; + } + return transactions; +} + + +TransactionInfo parseExpandedInfoHistoryBirbank(const QDomElement &root) { + if (root.isElement()) { + QDomElement elem = root.toElement(); + if (elem.tagName() == "node" + && elem.attribute("class") == "android.widget.Button" + && elem.attribute("text") == "Детали операции" + ) { + TransactionInfo transaction; + // Все что ниже деталей операции + QDomNodeList detailInfo = elem.parentNode().childNodes(); + + for (int i = 0; i < detailInfo.count(); ++i) { + QDomNode info = detailInfo.at(i); + if (!info.isElement()) continue; + + switch (QDomElement e = info.toElement(); e.attribute("index").toInt()) { + case 2: + // Дата операции + transaction.bankTime = transformBankDateTimeBirbank(findSubInfoBirbank(e, "1")); + break; + case 5: + transaction.description = findSubInfoBirbank(e, "1"); + parseTransferInfoBirbank(transaction.description, transaction); + break; + case 7: + // Комиссия + transaction.fee = findSubInfoBirbank(e, "1").remove(onlyDigitsRegex).toFloat(); + break; + default: + // qDebug() << "Прочее:" << findSubInfo2(e); + break; + } + } + return transaction; + } + } + + // Рекурсивно обходим всех детей + QDomNode child = root.firstChild(); + while (!child.isNull()) { + if (TransactionInfo info = parseExpandedInfoHistoryBirbank(child.toElement()); !info.bankTime.isEmpty()) { + return info; + } + child = child.nextSibling(); + } + + return {}; +} + +TransactionInfo ScreenXmlBirbankParser::parseTransactionInfoHistory(int screenWidth, int screenHeight) { + // 1. Распарсить описание транзакции + TransactionInfo transaction = parseExpandedInfoHistoryBirbank(m_xml); + // 1. Найти статус: Исполнен | Операция исполнена + transaction.status = TransactionStatus::Wait; + Node status1 = findTextNode("Исполнен", 0, 0, screenWidth, screenHeight / 2); + Node status2 = findTextNode("Операция исполнена", 0, 0, screenWidth, screenHeight / 2); + if (!status1.isEmpty() || !status2.isEmpty()) { + transaction.status = TransactionStatus::Complete; + } + return transaction; +} + +TransactionInfo parseExpandedInfoBirbank(const QDomElement &root) { + if (root.isElement()) { + QDomElement elem = root.toElement(); + if (elem.tagName() == "node" + && elem.attribute("class") == "android.widget.Button" + && elem.attribute("text") == "Детали операции" + ) { + TransactionInfo transaction; + // Все что ниже деталей операции + QDomNodeList detailInfo = elem.parentNode().childNodes(); + + for (int i = 0; i < detailInfo.count(); ++i) { + QDomNode info = detailInfo.at(i); + if (!info.isElement()) continue; + + QDomElement infoElement = info.toElement(); + QString index = infoElement.attribute("index"); + QDomElement ee; + switch (index.toInt()) { + case 3: + transaction.bankTime = transformBankDateTimeBirbank(findSubInfoBirbank(infoElement, "1")); + break; + case 4: + ee = findSubElementBirbank(infoElement, "0"); + ee = findSubElementBirbank(ee, "1"); + transaction.phone = findSubInfoBirbank(ee, "0").remove(onlyDigitsRegex); + break; + case 5: + transaction.name = findSubInfoBirbank(infoElement, "1"); + break; + case 6: + transaction.bankName = findSubInfoBirbank(infoElement, "1"); + break; + case 9: + transaction.fee = findSubInfoBirbank(infoElement, "1").remove(onlyDigitsAndDotRegex).toFloat(); + break; + case 10: + ee = findSubElementBirbank(infoElement, "1"); + transaction.bankTrExternalId = findSubInfoBirbank(ee, "1"); + break; + default: + break; + } + } + return transaction; + } + } + + // Рекурсивно обходим всех детей + QDomNode child = root.firstChild(); + while (!child.isNull()) { + if (TransactionInfo info = parseExpandedInfoBirbank(child.toElement()); !info.bankTime.isEmpty()) { + return info; + } + child = child.nextSibling(); + } + + return {}; +} + +TransactionInfo ScreenXmlBirbankParser::parseTransactionInfo(int screenWidth, int screenHeight) { + // 1. Распарсить описание транзакции + TransactionInfo transaction = parseExpandedInfoBirbank(m_xml); + // 1. Найти статус: Исполнен | Операция исполнена + transaction.status = TransactionStatus::Wait; + Node status1 = findTextNode("Исполнен", 0, 0, screenWidth, screenHeight / 2); + Node status2 = findTextNode("Операция исполнена", 0, 0, screenWidth, screenHeight / 2); + if (!status1.isEmpty() || !status2.isEmpty()) { + transaction.status = TransactionStatus::Complete; + } + return transaction; +} + + +QList ScreenXmlBirbankParser::parseAccountsInfo() { + const QDomNodeList nodes = m_xml.elementsByTagName("node"); + QList accounts; + + for (int i = 0; i < nodes.count(); ++i) { + const QDomElement el = nodes.at(i).toElement(); + if (el.attribute("text").contains("₼")) { + // Нашли сумму + // el.attribute("text") - ₼ и трим + auto amount = el.attribute("text").replace("₼", "").trimmed(); + const QDomElement parent = el.parentNode().parentNode().toElement(); + // Перебираем потомков + QDomNode n = parent.firstChild().firstChild().firstChild().firstChild(); + while (!n.isNull()) { + const QDomElement e = n.toElement(); + if (e.attribute("resource-id") == "az.kapitalbank.mbanking:id/tvNumber") { + const QString account = e.attribute("text"); + + AccountInfo info; + info.lastNumbers = account; + info.amount = amount.toFloat(); + info.description = account; + info.updateTime = DateUtils::getUtcNow(); + + accounts.append(info); + break; + } + n = n.nextSibling(); + } + } + } + return accounts; +} + +void ScreenXmlBirbankParser::test() { + // TransactionInfo info2 = parseExpandedInfo(m_xml); + // qDebug().noquote().nospace() << convertTransactionToString(info2); + // return; + + QFile file("assets/rshb/all_products.xml"); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qDebug() << "Cannot open file"; + } + + QDomDocument doc; + if (!doc.setContent(&file)) { + qDebug() << "Failed to parse XML"; + } + file.close(); + + qDebug() << "Start parsing XML:"; + + // Номер карты с последними цифрами + QDomElement root = doc.documentElement(); + parseAndSaveXml(doc.toString()); + // for (const AccountInfo &info: parseAccountsInfo()) { + // qDebug().noquote().nospace() << convertAccountToString(info); + // } + // TransactionInfo info = parseExpandedInfo(root); + // qDebug().noquote().nospace() << convertTransactionToString(info); + // ЕСПП +} diff --git a/android/birbank/ScreenXmlBirbankParser.h b/android/birbank/ScreenXmlBirbankParser.h new file mode 100644 index 0000000..f812aa8 --- /dev/null +++ b/android/birbank/ScreenXmlBirbankParser.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include "android/xml/Node.h" +#include "db/AccountInfo.h" +#include "db/TransactionInfo.h" + +class ScreenXmlBirbankParser final : public QObject { + Q_OBJECT + +public: + explicit ScreenXmlBirbankParser(QObject *parent = nullptr); + + ~ScreenXmlBirbankParser() override; + + Node tryToFindBannerOrDialog(int width, int height); + + Node tryToFindSecurityBanner(); + + Node tryToFindSelectBottomSheet(); + + Node findAccountButton(const QString &numbers); + + Node findTextNode(const QString &text); + + Node findTextNode(const QString &text, int x1, int y1, int x2, int y2); + + Node findEditText(const QString &text); + + Node findEditTextByResourceId(const QString &resourceId); + + Node findButtonNode(const QString &text, bool contains); + + bool isHomeScreen(); + + QList getLast2DaysTransactions(); + + void parseAndSaveXml(const QString &xml); + + TransactionInfo parseTransactionInfoHistory(int screenWidth, int screenHeight); + + TransactionInfo parseTransactionInfo(int screenWidth, int screenHeight); + + QList parsePositiveTransactions(); + + QList parseAccountsInfo(); + + QList findPinCodeNodes(const QString &pinCode); + + void test(); + +private: + QList m_nodes; + QDomElement m_xml; + +}; diff --git a/assets/birbank/banner/home_banner.png b/assets/birbank/banner/home_banner.png new file mode 100644 index 0000000..1ead70c Binary files /dev/null and b/assets/birbank/banner/home_banner.png differ diff --git a/assets/birbank/banner/home_banner.xml b/assets/birbank/banner/home_banner.xml new file mode 100644 index 0000000..f9180b9 --- /dev/null +++ b/assets/birbank/banner/home_banner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/birbank/home_page.png b/assets/birbank/home_page.png new file mode 100644 index 0000000..27c6daa Binary files /dev/null and b/assets/birbank/home_page.png differ diff --git a/assets/birbank/home_page.xml b/assets/birbank/home_page.xml new file mode 100644 index 0000000..e0db3e3 --- /dev/null +++ b/assets/birbank/home_page.xml @@ -0,0 +1,1950 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/birbank/payments_card.png b/assets/birbank/payments_card.png new file mode 100644 index 0000000..21a8730 Binary files /dev/null and b/assets/birbank/payments_card.png differ diff --git a/assets/birbank/payments_card.xml b/assets/birbank/payments_card.xml new file mode 100644 index 0000000..7689417 --- /dev/null +++ b/assets/birbank/payments_card.xml @@ -0,0 +1,798 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/birbank/payments_history.png b/assets/birbank/payments_history.png new file mode 100644 index 0000000..ba7c934 Binary files /dev/null and b/assets/birbank/payments_history.png differ diff --git a/assets/birbank/payments_history.xml b/assets/birbank/payments_history.xml new file mode 100644 index 0000000..a353dd4 --- /dev/null +++ b/assets/birbank/payments_history.xml @@ -0,0 +1,1122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/birbank/pin_code.png b/assets/birbank/pin_code.png new file mode 100644 index 0000000..efffc1c Binary files /dev/null and b/assets/birbank/pin_code.png differ diff --git a/assets/birbank/pin_code.xml b/assets/birbank/pin_code.xml new file mode 100644 index 0000000..2393a92 --- /dev/null +++ b/assets/birbank/pin_code.xml @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config.ini b/config.ini index 271c261..3467a69 100644 --- a/config.ini +++ b/config.ini @@ -10,6 +10,9 @@ currencies=USD, KRW, AZN, RUB token=token1753605586797 version=0.0.1 +[db] +dbPath=/Users/trnsmkot/CLionProjects/ARCDeskProject/ARCDeskProject/database/app_data.db + [net] accountUpdate=http://localhost:9999/api/v1/account/update appInfoUpdate=http://localhost:9999/api/v1/device/update diff --git a/database/app_data.db b/database/app_data.db index 42852bc..5f7c59d 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 f01ba15..611afb4 100644 --- a/database/dao/AccountDAO.cpp +++ b/database/dao/AccountDAO.cpp @@ -16,6 +16,7 @@ bool AccountDAO::upsertAccount(const AccountInfo &info) { DO UPDATE SET amount = excluded.amount, update_time = excluded.update_time, + last_numbers = excluded.last_numbers, status = excluded.status, description = excluded.description, currency = excluded.currency @@ -50,12 +51,14 @@ bool AccountDAO::updateAccountById( UPDATE accounts SET status = :status, description = :description, + last_numbers = :last_numbers, currency = :currency WHERE id = :id )"); query.bindValue(":status", status); query.bindValue(":description", description); + query.bindValue(":last_numbers", description.right(4)); query.bindValue(":currency", currency); query.bindValue(":id", id); diff --git a/main.cpp b/main.cpp index 2aad010..3459eea 100644 --- a/main.cpp +++ b/main.cpp @@ -167,6 +167,7 @@ int main(int argc, char *argv[]) { registrationWindow->close(); startNetworkService(app); startDeviceScreener(app); + startEventHandler(app); }, Qt::QueuedConnection); } else { auto *loader = new LoaderWindow(token); @@ -183,6 +184,7 @@ int main(int argc, char *argv[]) { mainWindow->show(); startNetworkService(app); startDeviceScreener(app); + startEventHandler(app); } else if (result == 0) { auto *registrationWindow = new RegistrationWindow(); registrationWindow->show(); @@ -195,6 +197,7 @@ int main(int argc, char *argv[]) { registrationWindow->close(); startNetworkService(app); startDeviceScreener(app); + startEventHandler(app); }, Qt::QueuedConnection); } loader->close(); @@ -202,8 +205,5 @@ int main(int argc, char *argv[]) { // loader.show(); } } - - - // startEventHandler(app); return QApplication::exec(); } diff --git a/services/EventHandler.cpp b/services/EventHandler.cpp index a3f5e56..cef1fd5 100644 --- a/services/EventHandler.cpp +++ b/services/EventHandler.cpp @@ -11,6 +11,7 @@ #include "dao/EventDAO.h" #include "db/ApplicationInfo.h" #include "net/NetworkService.h" +#include "birbank/GetLastDaysHistoryBirbankScript.h" #include "rshb/CommonScript.h" #include "rshb/GetLastDaysHistoryScript.h" #include "rshb/LoginAndCheckAccountsScript.h" @@ -123,36 +124,51 @@ void EventHandler::startThreadForKey(const QString &key) { const auto thr = new QThread(this); // qDebug() << "Start thread for key" << eventTypeToString(event.type); - if (event.type == EventType::Balance) { - auto *worker = new GetLastDaysHistoryScript(account); - worker->moveToThread(thr); + if (event.type == EventType::History) { + if ("birbank" == account.appCode) { + auto *worker = new GetLastDaysHistoryBirbankScript(account); + worker->moveToThread(thr); - connect(thr, &QThread::started, worker, &GetLastDaysHistoryScript::start); - connect(worker, &GetLastDaysHistoryScript::finished, thr, &QThread::quit); - connect(worker, &GetLastDaysHistoryScript::finished, worker, &QObject::deleteLater); - connect(thr, &QThread::finished, thr, &QObject::deleteLater); + connect(thr, &QThread::started, worker, &GetLastDaysHistoryBirbankScript::start); + connect(worker, &GetLastDaysHistoryBirbankScript::finished, thr, &QThread::quit); + connect(worker, &GetLastDaysHistoryBirbankScript::finished, worker, &QObject::deleteLater); + connect(thr, &QThread::finished, thr, &QObject::deleteLater); - connect(worker, &GetLastDaysHistoryScript::finishedWithResult, this, - [this, key, event](const QString &result) { - updateEventThread(key, event, result); - } - ); + connect(worker, &GetLastDaysHistoryBirbankScript::finishedWithResult, this, + [this, key, event](const QString &result) { + updateEventThread(key, event, result); + } + ); + } + // auto *worker = new GetLastDaysHistoryScript(account); + // worker->moveToThread(thr); + // + // connect(thr, &QThread::started, worker, &GetLastDaysHistoryScript::start); + // connect(worker, &GetLastDaysHistoryScript::finished, thr, &QThread::quit); + // connect(worker, &GetLastDaysHistoryScript::finished, worker, &QObject::deleteLater); + // connect(thr, &QThread::finished, thr, &QObject::deleteLater); + // + // connect(worker, &GetLastDaysHistoryScript::finishedWithResult, this, + // [this, key, event](const QString &result) { + // updateEventThread(key, event, result); + // } + // ); } if (event.type == EventType::Phone) { - auto *worker = new PayByPhoneScript(account, event.phone, event.bankName, event.amount); - worker->moveToThread(thr); - - connect(thr, &QThread::started, worker, &PayByPhoneScript::start); - connect(worker, &PayByPhoneScript::finished, thr, &QThread::quit); - connect(worker, &PayByPhoneScript::finished, worker, &QObject::deleteLater); - connect(thr, &QThread::finished, thr, &QObject::deleteLater); - - connect(worker, &PayByPhoneScript::finishedWithResult, this, - [this, key, event](const QString &result) { - updateEventThread(key, event, result); - } - ); + // auto *worker = new PayByPhoneScript(account, event.phone, event.bankName, event.amount); + // worker->moveToThread(thr); + // + // connect(thr, &QThread::started, worker, &PayByPhoneScript::start); + // connect(worker, &PayByPhoneScript::finished, thr, &QThread::quit); + // connect(worker, &PayByPhoneScript::finished, worker, &QObject::deleteLater); + // connect(thr, &QThread::finished, thr, &QObject::deleteLater); + // + // connect(worker, &PayByPhoneScript::finishedWithResult, this, + // [this, key, event](const QString &result) { + // updateEventThread(key, event, result); + // } + // ); } // Таймер на 5 минут — если не успел finish(), убить и перезапустить diff --git a/views/bank/AccountSettingsWindow.cpp b/views/bank/AccountSettingsWindow.cpp index c13383c..4c62262 100644 --- a/views/bank/AccountSettingsWindow.cpp +++ b/views/bank/AccountSettingsWindow.cpp @@ -11,6 +11,7 @@ #include #include +#include "birbank/LoginAndCheckAccountsBirbankScript.h" #include "common/PaddedItemDelegate.h" #include "dao/AccountDAO.h" #include "dao/ApplicationDAO.h" @@ -120,8 +121,23 @@ AccountSettingsWindow::AccountSettingsWindow( "background-color: orange;" "color: white;" ); - QObject::connect(soloButton, &QPushButton::clicked, []() { - qDebug() << "Кнопка 3 нажата"; + QObject::connect(soloButton, &QPushButton::clicked, [&, this]() { + if (!m_applicationInfo.pinCode.isEmpty()) { + QMessageBox msgBox(this); + msgBox.setWindowTitle(tr("Запустить проверку пин-кода?")); + msgBox.setText("Запуститься скрипт проверки входа в банк " + m_applicationInfo.name + " по пин-коду. Окно с настройками закроется."); + + QPushButton *deleteBtn = msgBox.addButton(tr("Запустить"), QMessageBox::AcceptRole); + QPushButton *closeBtn = msgBox.addButton(tr("Отмена"), QMessageBox::RejectRole); + msgBox.setDefaultButton(closeBtn); + msgBox.setModal(true); + msgBox.exec(); + + if (msgBox.clickedButton() == deleteBtn) { + startCheckPinCode(m_applicationInfo.code); + close(); + } + } }); layout->addWidget(soloButton); @@ -158,6 +174,7 @@ AccountSettingsWindow::AccountSettingsWindow( qDebug() << "Cant insert bank account data"; } else { reloadContent(tableWidget); + tableWidget->resizeColumnsToContents(); updateAccountData(accountInfo); } } @@ -179,6 +196,17 @@ AccountSettingsWindow::AccountSettingsWindow( } void AccountSettingsWindow::startCheckPinCode(const QString &appCode) { + if (appCode == "birbank") { + const auto thread = new QThread(nullptr); + auto *worker = new LoginAndCheckAccountsBirbankScript(m_deviceId, appCode, nullptr); + worker->moveToThread(thread); + connect(thread, &QThread::started, worker, &LoginAndCheckAccountsBirbankScript::start); + connect(worker, &LoginAndCheckAccountsBirbankScript::finished, thread, &QThread::quit); + connect(worker, &LoginAndCheckAccountsBirbankScript::finished, worker, &QObject::deleteLater); + connect(thread, &QThread::finished, thread, &QObject::deleteLater); + thread->start(); + } else if (appCode == "ziraat") { + } } void AccountSettingsWindow::sendAppStatus(const QString &deviceId, const QString &code, const QString &status) { @@ -301,6 +329,7 @@ void AccountSettingsWindow::reloadContent(QTableWidget *tableWidget) { accountInfo.currency = currency; accountInfo.description = cardNumber; accountInfo.amount = account.amount; + accountInfo.currency = currency; accountInfo.appCode = m_applicationInfo.code; accountInfo.deviceId = m_deviceId; accountInfo.status = on ? AccountStatus::Active : AccountStatus::Off;