From e2f8ac6c36720cc59399907e4c063919efe22834 Mon Sep 17 00:00:00 2001 From: trnsmkot Date: Tue, 17 Jun 2025 18:13:29 +0700 Subject: [PATCH] Add Ziraat Bank integration and support for IBAN payments This update introduces support for Ziraat Bank, including scripts for retrieving transaction history, parsing account information, and performing IBAN payments. Enhancements include improved screen parsing logic, additional transaction types, and better handling of amounts and IBAN-specific operations. --- android/ziraat/CommonZiraatScript.cpp | 132 ++--- android/ziraat/CommonZiraatScript.h | 10 +- .../ziraat/GetLastDaysHistoryZiraatScript.cpp | 160 ++++++ .../ziraat/GetLastDaysHistoryZiraatScript.h | 35 ++ .../LoginAndCheckAccountsZiraatScript.cpp | 69 +++ .../LoginAndCheckAccountsZiraatScript.h | 23 + android/ziraat/PayByIbanScript.cpp | 519 +++++++++--------- android/ziraat/PayByIbanScript.h | 16 +- android/ziraat/ScreenXmlZiraatParser.cpp | 194 ++++--- android/ziraat/ScreenXmlZiraatParser.h | 12 +- config.ini | 4 + database/time/DateUtils.cpp | 2 +- models/db/TransactionInfo.h | 3 + 13 files changed, 709 insertions(+), 470 deletions(-) create mode 100644 android/ziraat/GetLastDaysHistoryZiraatScript.cpp create mode 100644 android/ziraat/GetLastDaysHistoryZiraatScript.h create mode 100644 android/ziraat/LoginAndCheckAccountsZiraatScript.cpp create mode 100644 android/ziraat/LoginAndCheckAccountsZiraatScript.h diff --git a/android/ziraat/CommonZiraatScript.cpp b/android/ziraat/CommonZiraatScript.cpp index 059c1c7..50b927f 100644 --- a/android/ziraat/CommonZiraatScript.cpp +++ b/android/ziraat/CommonZiraatScript.cpp @@ -35,17 +35,6 @@ Node CommonZiraatScript::swipeToButton( return {}; } -bool CommonZiraatScript::inputPinCode(const QString &deviceId, const QList &pincode) { - for (const Node &node: pincode) { - const bool completePincode = AdbUtils::makeTap(deviceId, node.x(), node.y()); - if (!completePincode) { - AdbUtils::makeTap(deviceId, node.x(), node.y()); - } - QThread::msleep(QRandomGenerator::global()->bounded(100, 901)); - } - return true; -} - bool CommonZiraatScript::runAppAndGoToHomeScreen( const QString &deviceId, const QString &packageName, @@ -68,7 +57,7 @@ bool CommonZiraatScript::runAppAndGoToHomeScreen( // Даем минимальное время на запуск QThread::msleep(4500); int counter = 0; - bool findAndInputPincode = false; + bool findAndInputPassword = false; while (true) { counter++; @@ -80,40 +69,35 @@ bool CommonZiraatScript::runAppAndGoToHomeScreen( } xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); - if (xmlScreenParser.isHomeScreen()) { - return true; - } - - // Проверяем на банеры после пин-кодом - Node closeBannerOrDialogNode = xmlScreenParser.tryToFindBannerOrDialog(width, height); - if (closeBannerOrDialogNode.x() != -1 && closeBannerOrDialogNode.y() != -1) { - AdbUtils::makeTap(deviceId, closeBannerOrDialogNode.x(), closeBannerOrDialogNode.y()); - QThread::msleep(1000); - continue; - } - - // Проверяем на банеры перед пин-кодом - Node closeBannerNode = xmlScreenParser.tryToFindSecurityBanner(); + Node closeBannerNode = xmlScreenParser.findButtonNode("HAYIR", 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.tryToFindPinCode(pinCode, "Введите ПИН"); - pincode.size() == 4) { - // чтобы пробудить экран - if (Node textPinCodeNode = xmlScreenParser.findTextNode("Введите ПИН"); !textPinCodeNode.isEmpty()) { - AdbUtils::makeTap(deviceId, textPinCodeNode.x(), textPinCodeNode.y()); + if (!findAndInputPassword) { + Node loginNode = xmlScreenParser.findButtonNode("Login", false); + if (!loginNode.isEmpty()) { + AdbUtils::makeTap(deviceId, loginNode.x(), loginNode.y()); + QThread::msleep(500); + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); + + Node passwordEditText = xmlScreenParser.findEditText("Password"); + if (!passwordEditText.isEmpty()) { + AdbUtils::makeTap(deviceId, passwordEditText.x(), passwordEditText.y()); + QThread::msleep(500); + AdbUtils::inputText(deviceId, pinCode); + + findAndInputPassword = true; + QThread::msleep(3000); + continue; } - inputPinCode(deviceId, pincode); - // После ввода пин-код нужно сделать задержку, пока подгрузка идет, чтобы снова не спарсить - findAndInputPincode = true; - QThread::msleep(3000); - continue; } } @@ -121,83 +105,30 @@ bool CommonZiraatScript::runAppAndGoToHomeScreen( } } -bool CommonZiraatScript::tryToCloseAllBannersOnHomePage(const QString &deviceId, const int width, const int height) { - Node closeBannerOrDialogNode = xmlScreenParser.tryToFindBannerOrDialog(width, height); - if (closeBannerOrDialogNode.x() != -1 && closeBannerOrDialogNode.y() != -1) { - AdbUtils::makeTap(deviceId, closeBannerOrDialogNode.x(), closeBannerOrDialogNode.y()); - return true; - } - - // TODO тут все другие банеры попробовать закрыть - return false; -} - bool CommonZiraatScript::goToAllProducts( const QString &deviceId, const int width, const int height ) { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, m_counter)); - if (tryToCloseAllBannersOnHomePage(deviceId, width, height)) { + Node closeBannerNode = xmlScreenParser.findButtonNode("HAYIR", false); + if (!closeBannerNode.isEmpty()) { + AdbUtils::makeTap(deviceId, closeBannerNode.x(), closeBannerNode.y()); QThread::msleep(500); - xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); } if (xmlScreenParser.isHomeScreen()) { - // идем на экран "Все продукты" - if (Node allCardTitle = xmlScreenParser.findTextNode("Счета и карты"); !allCardTitle.isEmpty()) { - // 1. Если нет кнопки видимой, свайпаем. Кликаем на "Все продукты" - for (int i = 0; i < 5; ++i) { - Node allProductsBtn = xmlScreenParser.findButtonNode("Все продукты", false); - if (allProductsBtn.x() > 0 && allProductsBtn.y() > 0 && allProductsBtn.y() < height - 200) { - AdbUtils::makeTap(deviceId, allProductsBtn.x(), allProductsBtn.y()); - QThread::msleep(1000); - break; - } - AdbUtils::makeSwipe(deviceId, - allCardTitle.x(), allCardTitle.y(), - allCardTitle.x(), allCardTitle.y() - 200); - xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); - } - - for (int i = 0; i < 5; ++i) { - Node allProductsTitle = xmlScreenParser.findTextNode("Все продукты"); - // Проверяем что вверху заголовок - if (!allProductsTitle.isEmpty() && allProductsTitle.y() < 300) { - return true; - } - - QThread::msleep(1000); - xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); - } - } else { - // FIXME пишем в БД, что не дошли до домашней страницы, и завершаемся - // сохраняем скрин приложения - // дделаем алерт - } - } else { - // FIXME - qDebug() << "Not home screen..."; - } - return false; -} - -bool CommonZiraatScript::findAndTapOnAccount(const QString &deviceId, const QString &accountNumbers) { - QList > accounts = xmlScreenParser.parseAccountsInfo(); - for (auto &[account, node]: accounts) { - // FIXME будем считать что числа последние везде уникальные - if (accountNumbers == account.lastNumbers) { - qDebug() << "findAndTapOnAccount: " << accountNumbers; - AdbUtils::makeTap(deviceId, node.x(), node.y()); - QThread::msleep(1000); + 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 CommonZiraatScript::postAccountsData(const QList > &accounts) { +void CommonZiraatScript::postAccountsData(const QList &accounts) { if (accounts.isEmpty()) { return; } @@ -206,9 +137,8 @@ void CommonZiraatScript::postAccountsData(const QList > auto *worker = new NetworkService; QJsonArray list; - for (const QPair &pair: accounts) { + for (const AccountInfo &account: accounts) { QJsonObject obj; - AccountInfo account = pair.first; obj["appName"] = account.appCode; obj["lastNumbers"] = account.lastNumbers; obj["amount"] = account.amount; @@ -218,7 +148,7 @@ void CommonZiraatScript::postAccountsData(const QList > list.append(obj); } // FIXME доставить десктоп ид из настроеек - worker->setDeviceId(accounts.first().first.deviceId); + worker->setDeviceId(accounts.first().deviceId); worker->setPayload(QJsonDocument(list).toJson()); worker->moveToThread(thread); connect(thread, &QThread::started, worker, &NetworkService::postAccountsData); diff --git a/android/ziraat/CommonZiraatScript.h b/android/ziraat/CommonZiraatScript.h index d7d2b7b..65d0c54 100644 --- a/android/ziraat/CommonZiraatScript.h +++ b/android/ziraat/CommonZiraatScript.h @@ -14,17 +14,13 @@ public: protected: virtual void doStart() = 0; - static bool inputPinCode(const QString &deviceId, const QList &pincode); - bool goToAllProducts( const QString &deviceId, int width, int height ); - bool findAndTapOnAccount(const QString &deviceId, const QString &accountNumbers); - - void postAccountsData(const QList > &accounts); + void postAccountsData(const QList &accounts); void postNewTransactionData(const AccountInfo &account, const TransactionInfo &transaction); @@ -50,10 +46,6 @@ protected: int height ); - bool tryToCloseAllBannersOnHomePage( - const QString &deviceId, int width, int height - ); - bool m_running = true; ScreenXmlZiraatParser xmlScreenParser; int m_counter = 0; diff --git a/android/ziraat/GetLastDaysHistoryZiraatScript.cpp b/android/ziraat/GetLastDaysHistoryZiraatScript.cpp new file mode 100644 index 0000000..f3befa2 --- /dev/null +++ b/android/ziraat/GetLastDaysHistoryZiraatScript.cpp @@ -0,0 +1,160 @@ +#include "GetLastDaysHistoryZiraatScript.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" + +GetLastDaysHistoryZiraatScript::GetLastDaysHistoryZiraatScript( + AccountInfo account, + QObject *parent +) : CommonZiraatScript(parent), + m_account(std::move(account)) { +} + +GetLastDaysHistoryZiraatScript::~GetLastDaysHistoryZiraatScript() = default; + +bool GetLastDaysHistoryZiraatScript::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 = transaction.description.startsWith("Перевод") ? TransactionType::Phone : TransactionType::Card; + tr.completeTime = DateUtils::getUtcNow(); + tr.id = TransactionDAO::insertTransaction(tr); + + if (tr.id == -1) { + return false; + } + postNewTransactionData(account, tr); + return true; +} + +bool GetLastDaysHistoryZiraatScript::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 GetLastDaysHistoryZiraatScript::doStart() { + // берем все транзакции из БД + const DeviceInfo device = DeviceDAO::getDeviceById(m_account.deviceId); + const ApplicationInfo app = ApplicationDAO::getApplication(m_account.deviceId, m_account.appCode); + + 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("HAYIR", 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()) { + if (goToAllProducts(device.id, device.screenWidth, device.screenHeight)) { + QList localTransactions = TransactionDAO::getTransactionsWithinHours(m_account.id, 48); + + QList accounts = xmlScreenParser.parseAccountsInfo(); + for (auto &[account, node]: accounts) { + account.deviceId = m_account.deviceId; + account.appCode = m_account.appCode; + if (!AccountDAO::upsertAccount(account)) { + qDebug() << "Not updated: " << account.lastNumbers; + } + } + postAccountsData(accounts); + AdbUtils::goBack(device.id); + QThread::msleep(500); + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_account.deviceId, ++m_counter)); + + Node actionBtn = xmlScreenParser.findButtonNode("Account Activities", false); + if (!actionBtn.isEmpty()) { + AdbUtils::makeTap(m_account.deviceId, actionBtn.x(), actionBtn.y()); + QThread::msleep(1000); + } else { + m_error = "Account Activities btn not found"; + qCritical() << m_error; + emit finishedWithResult(m_error); + return; + } + + // localTransactions + // FIXME доделать так, чтобы свайпать и брать больше чем 5 транзакций + for (int i = 0; i < 5; ++i) { + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_account.deviceId, ++m_counter)); + QList transactions = xmlScreenParser.parseTodayAndYesterdayReportHistory(); + if (!transactions.isEmpty()) { + break; + } + } + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_account.deviceId, ++m_counter)); + // все подтвержденные + QList parsedTransactions = xmlScreenParser.parseTodayAndYesterdayReportHistory(); + + QSet parsedTransactionIds; + for (const TransactionInfo &transaction: parsedTransactions) { + for (const TransactionInfo &saved: localTransactions) { + if (parsedTransactionIds.contains(saved.id)) { + continue; + } + + if (transaction.bankTime == saved.bankTime && transaction.amount == saved.amount) { + parsedTransactionIds.insert(saved.id); + continue; + } + + if (!saveAndPostNewTransactionData(m_account, transaction, TransactionStatus::Complete)) { + qDebug() << "--- saveAndPostNewTransactionData failed"; + } + } + } + } + } else { + m_error = "GetLastDaysHistoryZiraatScript - home screen not found"; + qWarning() << m_error; + emit finishedWithResult(m_error); + } + AdbUtils::goBack(m_account.deviceId); + emit finishedWithResult(m_error); +} diff --git a/android/ziraat/GetLastDaysHistoryZiraatScript.h b/android/ziraat/GetLastDaysHistoryZiraatScript.h new file mode 100644 index 0000000..97eff06 --- /dev/null +++ b/android/ziraat/GetLastDaysHistoryZiraatScript.h @@ -0,0 +1,35 @@ +#pragma once +#include "CommonZiraatScript.h" + +class GetLastDaysHistoryZiraatScript final : public CommonZiraatScript { + Q_OBJECT + +public: + ~GetLastDaysHistoryZiraatScript() override; + + explicit GetLastDaysHistoryZiraatScript( + 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/ziraat/LoginAndCheckAccountsZiraatScript.cpp b/android/ziraat/LoginAndCheckAccountsZiraatScript.cpp new file mode 100644 index 0000000..fceb424 --- /dev/null +++ b/android/ziraat/LoginAndCheckAccountsZiraatScript.cpp @@ -0,0 +1,69 @@ +#include "LoginAndCheckAccountsZiraatScript.h" + +#include "adb/AdbUtils.h" +#include "dao/AccountDAO.h" +#include "dao/ApplicationDAO.h" +#include "dao/DeviceDAO.h" + +LoginAndCheckAccountsZiraatScript::LoginAndCheckAccountsZiraatScript( + QString deviceId, + QString appCode, + QObject *parent +) : CommonZiraatScript(parent), + m_deviceId(std::move(deviceId)), m_appCode(std::move(appCode)) { +} + +LoginAndCheckAccountsZiraatScript::~LoginAndCheckAccountsZiraatScript() = default; + + +void LoginAndCheckAccountsZiraatScript::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; + 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; + } + + if (goToAllProducts(device.id, device.screenWidth, device.screenHeight)) { + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter)); + + QList accounts = xmlScreenParser.parseAccountsInfo(); + for (auto account: accounts) { + account.deviceId = m_deviceId; + account.appCode = m_appCode; + account.status = AccountStatus::Active; + if (!AccountDAO::upsertAccount(account)) { + m_error = "Not updated: " + account.lastNumbers; + qCritical() << m_error; + } + } + + postAccountsData(accounts); + } else { + qWarning() << "Cant go to all products: " << m_deviceId << " " << m_appCode; + } + } + + AdbUtils::tryToKillApplication(m_deviceId, app.package); + emit finishedWithResult(m_error); +} diff --git a/android/ziraat/LoginAndCheckAccountsZiraatScript.h b/android/ziraat/LoginAndCheckAccountsZiraatScript.h new file mode 100644 index 0000000..6b7a638 --- /dev/null +++ b/android/ziraat/LoginAndCheckAccountsZiraatScript.h @@ -0,0 +1,23 @@ +#pragma once +#include "CommonZiraatScript.h" + +class LoginAndCheckAccountsZiraatScript final : public CommonZiraatScript { + Q_OBJECT + +public: + ~LoginAndCheckAccountsZiraatScript() override; + + explicit LoginAndCheckAccountsZiraatScript( + QString deviceId, + QString appCode, + QObject *parent = nullptr + ); + +protected: + void doStart() override; + +private: + const QString m_deviceId; + const QString m_appCode; + QString m_error; +}; \ No newline at end of file diff --git a/android/ziraat/PayByIbanScript.cpp b/android/ziraat/PayByIbanScript.cpp index 5df1df7..977b8a5 100644 --- a/android/ziraat/PayByIbanScript.cpp +++ b/android/ziraat/PayByIbanScript.cpp @@ -12,262 +12,259 @@ #include "dao/TransactionDAO.h" #include "db/ApplicationInfo.h" #include "db/DeviceInfo.h" +#include "time/DateUtils.h" PayByIbanScript::PayByIbanScript( AccountInfo account, - QString phone, - QString bankName, + QString iban, + QString fullName, const double amount, QObject *parent ) : CommonZiraatScript(parent), - m_account(std::move(account)), m_phone(std::move(phone)), - m_bankName(std::move(bankName)), m_amount(amount) { + m_account(std::move(account)), m_iban(std::move(iban)), + m_fullName(std::move(fullName)), m_amount(amount) { } PayByIbanScript::~PayByIbanScript() = default; -void PayByIbanScript::makePayment( - const QString &deviceId, - const QString &pinCode, - int width, - int height -) { +void PayByIbanScript::makePayment(const DeviceInfo &device) { // Оплата со страницы карты - xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); - Node payBtn = xmlScreenParser.findButtonNode("Оплатить", false); - if (!payBtn.isEmpty()) { - AdbUtils::makeTap(deviceId, payBtn.x(), payBtn.y()); - QThread::msleep(1000); - } else { - AdbUtils::takeScreenshot(deviceId, "pay_by_phone_pay_btn_not_found"); - m_error = "Кнопка 'Оплатить' не нашлась"; - qDebug() << m_error; - return; - } + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter)); + Node editIbanEditText = xmlScreenParser.findEditTextByResourceId("com.ziraat.ziraatmobil:id/edit_iban"); + Node fullNameEditText = xmlScreenParser.findEditTextByResourceId("com.ziraat.ziraatmobil:id/edit_full_name"); + if (!editIbanEditText.isEmpty() && !fullNameEditText.isEmpty()) { + AdbUtils::makeTap(device.id, editIbanEditText.x(), editIbanEditText.y()); + QThread::msleep(500); + AdbUtils::inputText(device.id, m_iban); - xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); - // TODO - По номеру карты В РСХБ и другие банки - Node payTitle = xmlScreenParser.findTextNode("Платежи и переводы"); - if (!payTitle.isEmpty() && payTitle.y() < 300) { - Node payByPhoneBtn = xmlScreenParser.findButtonNode("По номеру телефона В РСХБ и через", false); - if (!payByPhoneBtn.isEmpty()) { - AdbUtils::makeTap(deviceId, payByPhoneBtn.x(), payByPhoneBtn.y()); + AdbUtils::makeTap(device.id, editIbanEditText.x(), editIbanEditText.y()); + QThread::msleep(500); + AdbUtils::inputText(device.id, m_fullName); + + Node nextBtn = xmlScreenParser.findButtonNode("NEXT", false); + AdbUtils::makeTap(device.id, nextBtn.x(), nextBtn.y()); + + bool findSenderAccount = false; + for (int i = 0; i < 5; ++i) { QThread::msleep(1000); - } else { - AdbUtils::takeScreenshot(deviceId, "pay_by_phone_not_found_pay_by_phone"); - m_error = "Не смогли найти пункт 'По номеру телефона В РСХБ и через'"; + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter)); + + Node senderAccount = xmlScreenParser.findTextNode("Sender Account"); + if (!senderAccount.isEmpty()) { + Node inputBankName = xmlScreenParser.findAccountButton(m_account.lastNumbers); + if (!inputBankName.isEmpty()) { + AdbUtils::makeTap(device.id, inputBankName.x(), inputBankName.y()); + QThread::msleep(500); + + + // FXIME скорее всего придется нажать кнопку NEXT + findSenderAccount = true; + } + break; + } + } + if (!findSenderAccount) { + m_error = "Не нашли Sender Account"; qDebug() << m_error; return; } - } else { - AdbUtils::takeScreenshot(deviceId, "pay_by_phone_cant_go_to_payments"); - m_error = "Не смогли перейти на экран 'Платежи и переводы'"; - qDebug() << m_error; - return; - } - - xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); - Node payByPhoneTitle = xmlScreenParser.findTextNode("Перевод по телефону"); - Node continueBtn = xmlScreenParser.findButtonNode("Продолжить", false); - if (!payByPhoneTitle.isEmpty() && payByPhoneTitle.y() < 300 && continueBtn.isEmpty()) { - Node inputPhone = xmlScreenParser.findFirstEditText(); - AdbUtils::makeTap(deviceId, inputPhone.x(), inputPhone.y()); - QThread::msleep(500); - AdbUtils::inputText(deviceId, m_phone); - QThread::msleep(1000); - } else { - AdbUtils::takeScreenshot(deviceId, "pay_by_phone_cant_find_pay_by_phone"); - m_error = "Не смогли найти пункт 'Перевод по телефону'"; - qDebug() << m_error; - return; - } - - xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); - QString payBankName = m_bankName == QStringLiteral("Россельхозбанк") ? "Россельхозбанк" : "Другой банк через СБП"; - Node payOtherBankBtn = xmlScreenParser.findButtonNode(payBankName, false); - if (!payOtherBankBtn.isEmpty()) { - AdbUtils::makeTap(deviceId, payOtherBankBtn.x(), payOtherBankBtn.y()); - QThread::msleep(1000); - } else { - AdbUtils::takeScreenshot(deviceId, "pay_by_phone_cant_tap_on_bank"); - m_error = "Не смогли нажать на первичный выбор банка"; - qDebug() << m_error; - return; - } - - if (m_bankName != QStringLiteral("Россельхозбанк")) { - xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); + bool findAmountDetermination = false; for (int i = 0; i < 5; ++i) { - Node allBanksTitle = xmlScreenParser.findTextNode("Банки участники"); - if (!allBanksTitle.isEmpty() && allBanksTitle.y() < 300) { - Node inputBankName = xmlScreenParser.findFirstEditText(); - AdbUtils::makeTap(deviceId, inputBankName.x(), inputBankName.y()); - QThread::msleep(500); - AdbUtils::enableAdbIMEKeyboard(deviceId); - AdbUtils::inputAdbIMEText(deviceId, m_bankName); - AdbUtils::disableAdbIMEKeyboard(deviceId); - QThread::msleep(1000); - break; - } else { - qDebug() << "--- allBanksTitle not found, i: " << i; - } QThread::msleep(1000); - xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); - } + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter)); - xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); - for (int i = 0; i < 5; ++i) { - Node bankNameListItem = xmlScreenParser.findTextNode(m_bankName); - if (!bankNameListItem.isEmpty()) { - AdbUtils::makeTap(deviceId, bankNameListItem.x(), bankNameListItem.y()); - QThread::msleep(1000); - break; - } else { - qDebug() << "--- bankNameListItem not found, i: " << i; - } - QThread::msleep(1000); - xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); - } - } + Node amountDetermination = xmlScreenParser.findTextNode("Amount Determination"); + if (!amountDetermination.isEmpty()) { + Node integerEditText = xmlScreenParser.findEditTextByResourceId( + "com.ziraat.ziraatmobil:id/edit_text_integer"); + Node decimalEditText = xmlScreenParser.findEditTextByResourceId( + "com.ziraat.ziraatmobil:id/edit_text_decimal"); + if (!integerEditText.isEmpty() && !decimalEditText.isEmpty()) { + int integerPart = static_cast(m_amount); + AdbUtils::makeTap(device.id, integerEditText.x(), integerEditText.y()); + QThread::msleep(500); + AdbUtils::inputText(device.id, QString::number(integerPart)); - xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); + int decimalPart = static_cast((m_amount - integerPart) * 100); + AdbUtils::makeTap(device.id, decimalEditText.x(), decimalEditText.y()); + QThread::msleep(500); + AdbUtils::inputText(device.id, QString::number(decimalPart)); - const bool isRshbBank = m_bankName == QStringLiteral("Россельхозбанк"); - QString payByPhoneScreenTitleText = isRshbBank ? "Перевод клиенту РСХБ" : "Перевод по телефону"; - Node payByPhoneScreenTitle = xmlScreenParser.findTextNode(payByPhoneScreenTitleText); - Node continuePayBtn = xmlScreenParser.findButtonNode("Продолжить", false); - - if (isRshbBank || - !payByPhoneScreenTitle.isEmpty() && payByPhoneScreenTitle.y() < 300 && !continuePayBtn.isEmpty()) { - Node sumBtn = xmlScreenParser.findButtonNode("0", false); - if (isRshbBank || !sumBtn.isEmpty()) { - if (!isRshbBank) { - AdbUtils::makeTap(deviceId, sumBtn.x(), sumBtn.y()); - QThread::msleep(500); - } - AdbUtils::inputText(deviceId, QString::number(m_amount)); - QThread::msleep(500); - - xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); - Node readyBtn = xmlScreenParser.findTextNode("Готово"); - if (!readyBtn.isEmpty()) { - AdbUtils::makeTap(deviceId, readyBtn.x(), readyBtn.y()); - QThread::msleep(500); - if (isRshbBank) { - QThread::msleep(1000); - xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); - continuePayBtn = xmlScreenParser.findButtonNode("Продолжить", false); + findAmountDetermination = true; + break; + } else { + m_error = "Не нашли поля ввода для ввода суммы перевода"; + qDebug() << m_error; + return; + } + } + } + if (!findAmountDetermination) { + m_error = "Не нашли Amount Determination"; + qDebug() << m_error; + return; + } + + bool findDetailDefinition = false; + for (int i = 0; i < 5; ++i) { + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter)); + Node fastTransferBtn = xmlScreenParser.findButtonNode("FAST TRANSFER", false); + if (!fastTransferBtn.isEmpty()) { + AdbUtils::makeTap(device.id, fastTransferBtn.x(), fastTransferBtn.y()); + QThread::msleep(500); + continue; + } + + Node detailDefinitionTitle = xmlScreenParser.findTextNode("Detail Definition"); + Node pleaseChooseBtn = xmlScreenParser.findButtonNode("Please choose", false); + if (!detailDefinitionTitle.isEmpty() && !pleaseChooseBtn.isEmpty()) { + AdbUtils::makeTap(device.id, pleaseChooseBtn.x(), pleaseChooseBtn.y()); + QThread::msleep(500); + + Node searchEditText = xmlScreenParser. + findEditTextByResourceId("com.ziraat.ziraatmobil:id/input_search"); + if (!searchEditText.isEmpty()) { + AdbUtils::makeTap(device.id, searchEditText.x(), searchEditText.y()); + QThread::msleep(500); + AdbUtils::inputText(device.id, "Other Payments"); + QThread::msleep(1000); + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter)); + + Node otherPaymentsText = xmlScreenParser.findTextNode("Other Payments"); + if (!otherPaymentsText.isEmpty()) { + AdbUtils::makeTap(device.id, otherPaymentsText.x(), otherPaymentsText.y()); + QThread::msleep(500); + findDetailDefinition = true; + break; + } + } else { + for (int i = 0; i < 5; ++i) { + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter)); + Node otherPaymentsText = xmlScreenParser.findTextNode("Other Payments"); + if (otherPaymentsText.isEmpty()) { + AdbUtils::makeSwipe( + m_account.deviceId, + device.screenWidth / 2, device.screenHeight / 4 * 3, + device.screenWidth / 2, device.screenHeight / 4 + ); + } else { + AdbUtils::makeTap(device.id, otherPaymentsText.x(), otherPaymentsText.y()); + QThread::msleep(500); + findDetailDefinition = true; + break; + } + QThread::msleep(500); + } + } + } + } + + if (!findDetailDefinition) { + m_error = "Не нашли Detail Definition"; + qDebug() << m_error; + return; + } + + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter)); + Node nextBtn4 = xmlScreenParser.findButtonNode("NEXT", false); + AdbUtils::makeTap(device.id, nextBtn4.x(), nextBtn4.y()); + QThread::msleep(500); + + Node checkInfo = xmlScreenParser.findTextNode( + "Check the details of the transaction you shall realize and approve your transaction."); + if (!checkInfo.isEmpty()) { + for (int i = 0; i < 5; ++i) { + Node confirmBtn = xmlScreenParser.findButtonNode("CONFIRM", false); + if (confirmBtn.isEmpty()) { + AdbUtils::makeSwipe( + m_account.deviceId, + device.screenWidth / 2, device.screenHeight / 4 * 3, + device.screenWidth / 2, device.screenHeight / 4 + ); + } else { + AdbUtils::makeTap(device.id, confirmBtn.x(), confirmBtn.y()); + QThread::msleep(500); + break; + } + + QThread::msleep(500); + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter)); + } + + + QThread::msleep(1000); + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter)); + Node successText = xmlScreenParser.findTextNode("Your transaction is successfully realized."); + if (!successText.isEmpty()) { + Node receiptBtn = xmlScreenParser.findButtonNode("RECEIPT", false); + if (!receiptBtn.isEmpty()) { + AdbUtils::makeTap(device.id, receiptBtn.x(), receiptBtn.y()); + QThread::msleep(500); + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter)); + + Node sendBtn = xmlScreenParser.findButtonNode("SEND E-MAIL", false); + if (!sendBtn.isEmpty()) { + AdbUtils::takeXmlDump( + device.id, "pay_by_iban_finish_transactions_" + QString::number(m_account.id) + ); + // Успешная транзакция + // TODO считываем все данные по переводу + TransactionInfo transaction; + transaction.name = m_fullName; + transaction.accountId = m_account.id; + transaction.amount = -m_amount; + transaction.fee = 0; + transaction.status = TransactionStatus::Complete; + transaction.type = TransactionType::Iban; + transaction.updateTime = DateUtils::getUtcNow(); + transaction.completeTime = DateUtils::getUtcNow(); + transaction.timestamp = DateUtils::getUtcNow(); + transaction.id = TransactionDAO::insertTransaction(transaction); + if (transaction.id == -1) { + // FIXME alert!! + qDebug() << "Transaction has not inserted..."; + } + + postNewTransactionData(m_account, transaction); + + Node backBtn = xmlScreenParser.findButtonNode("BACK", false); + if (!backBtn.isEmpty()) { + AdbUtils::makeTap(device.id, backBtn.x(), backBtn.y()); + QThread::msleep(500); + + Node returnBtn = xmlScreenParser.findButtonNode("RETURN TO HOME PAGE", false); + if (!returnBtn.isEmpty()) { + AdbUtils::makeTap(device.id, returnBtn.x(), returnBtn.y()); + } + } + } else { + //FIXME не успешная транзакция + AdbUtils::takeXmlDump( + device.id, "pay_by_iban_fail_transactions_receipt_" + QString::number(m_account.id) + ); + m_error = "Транзакция не успешна"; + qDebug() << m_error; + return; + } } - AdbUtils::makeTap(deviceId, continuePayBtn.x(), continuePayBtn.y()); - QThread::msleep(1000); } else { - AdbUtils::takeScreenshot(deviceId, "pay_by_phone_cant_tap_on_ready"); - m_error = "Не смогли нажать 'Готово'"; + m_error = "Транзакция не успешна"; qDebug() << m_error; return; } } else { - AdbUtils::takeScreenshot(deviceId, "pay_by_phone_cant_tap_on_continue"); - m_error = "Не смогли нажать 'Продолжить'"; + m_error = "Не нашли Check the details title info"; qDebug() << m_error; return; } } else { - AdbUtils::takeScreenshot(deviceId, "pay_by_phone_cant_find_bank_title"); - m_error = "Не смогли найти заголовок: " + payByPhoneScreenTitleText; + m_error = "Не нашли поля ввода для IBAN"; qDebug() << m_error; return; } - - xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); - bool inputedPinCode = false; - for (int i = 0; i < 5; ++i) { - // TODO кнопка содержит "Перевести 10 ₽" если будет от 1000 то скорее всвего будет "Перевести 1 000 ₽", - // TODO не ясно как форматирование будет, поэтому пока на начало зашьем - Node makePayBtn = xmlScreenParser.findButtonNode("Перевести ", true); - if (!makePayBtn.isEmpty()) { - AdbUtils::makeTap(deviceId, makePayBtn.x(), makePayBtn.y()); - QThread::msleep(500); - - xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); - if (const QList pincode = xmlScreenParser.tryToFindPinCode(pinCode, "Введите код доступа"); - pincode.size() == 4) { - inputPinCode(deviceId, pincode); - inputedPinCode = true; - // После ввода пин-кода, ждем 3с - QThread::msleep(3000); - } - break; - } else { - qWarning() << "--- makePayBtn not found, i: " << i; - } - QThread::msleep(1000); - xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); - } - - if (!isRshbBank && !inputedPinCode) { - AdbUtils::takeScreenshot(deviceId, "pay_by_phone_cant_input_pin_code"); - m_error = "Не смогли ввести пин-код!'"; - qDebug() << m_error; - return; - } - - xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); - - for (int i = 0; i < 10; ++i) { - Node moreDetailBtn = xmlScreenParser.findButtonNode("Детали операции", false); - if (!moreDetailBtn.isEmpty()) { - AdbUtils::makeTap(deviceId, moreDetailBtn.x(), moreDetailBtn.y()); - QThread::msleep(200); - xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); - - // TODO считываем все данные по переводу - TransactionInfo transaction = xmlScreenParser.parseTransactionInfo(width, height); - if (isRshbBank) { - transaction.bankName = m_bankName; - } - transaction.accountId = m_account.id; - transaction.amount = -m_amount; - transaction.type = TransactionType::Phone; - transaction.id = TransactionDAO::insertTransaction(transaction); - if (transaction.id == -1) { - // FIXME alert!! - qDebug() << "Transaction has not inserted..."; - } - - // ждем 10 секунд смену статуса - for (int k = 0; k < 10; ++k) { - Node paymentCompleted = xmlScreenParser.findTextNode("Исполнен", 0, 0, width, height / 2); - if (!paymentCompleted.isEmpty()) { - transaction.status = TransactionStatus::Complete; - if (!TransactionDAO::updateTransactionStatus(transaction.id, TransactionStatus::Complete)) { - qDebug() << "Not updated: " << transaction.id; - } - // TODO обновляем данные - break; - } - - QThread::msleep(1000); - xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); - } - - postNewTransactionData(m_account, transaction); - - AdbUtils::takeScreenshot(deviceId, "pay_by_phone_finish_transactions_" + transaction.id); - AdbUtils::takeXmlDump(deviceId, "pay_by_phone_finish_transactions_" + transaction.id); - break; - } else { - qWarning() << "--- moreDetailBtn not found, i: " << i; - } - QThread::msleep(1000); - xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); - } - - Node homeBtn = xmlScreenParser.findButtonNode("Вернуться на главную", false); - AdbUtils::makeTap(deviceId, homeBtn.x(), homeBtn.y()); - QThread::msleep(1000); } void PayByIbanScript::doStart() { @@ -291,46 +288,62 @@ void PayByIbanScript::doStart() { } } - if (goToAllProducts(device.id, device.screenWidth, device.screenHeight)) { - QList > accounts = xmlScreenParser.parseAccountsInfo(); - for (auto &[account, node]: accounts) { - account.deviceId = m_account.deviceId; - account.appCode = m_account.appCode; - account.status = account.amount > 0 ? AccountStatus::Active : AccountStatus::Blocked; - if (!AccountDAO::upsertAccount(account)) { - qDebug() << "Not updated: " << account.lastNumbers; - } - } + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_account.deviceId, ++m_counter)); + Node allTransactionsNav = xmlScreenParser.findTextNode("Transactions"); + if (!allTransactionsNav.isEmpty()) { + AdbUtils::makeTap(device.id, allTransactionsNav.x(), allTransactionsNav.y()); + QThread::msleep(1000); + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_account.deviceId, ++m_counter)); - postAccountsData(accounts); + // перешли на страницу транзакций + Node allTransactionsTitle = xmlScreenParser.findTextNode( + "Transactions", 0, 0, device.screenWidth, device.screenHeight / 2 + ); + Node moneyTransfersBtn = xmlScreenParser.findTextNode("Money Transfers"); + if (!allTransactionsTitle.isEmpty() && !moneyTransfersBtn.isEmpty()) { + AdbUtils::makeTap(device.id, moneyTransfersBtn.x(), moneyTransfersBtn.y()); + QThread::msleep(1000); + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_account.deviceId, ++m_counter)); - for (auto &[account, node]: accounts) { - // FIXME будем считать что числа последние везде уникальные - if (m_account.lastNumbers == account.lastNumbers) { - if (account.amount >= m_amount) { - AdbUtils::makeTap(device.id, node.x(), node.y()); + // перешли на страницу переводов + Node moneyTransfersTitle = xmlScreenParser.findTextNode( + "Money Transfers", 0, 0, device.screenWidth, device.screenHeight / 2 + ); + Node transferToOtherAccountBtn = xmlScreenParser.findTextNode("Transfer to Other Account"); + if (!moneyTransfersTitle.isEmpty() && !transferToOtherAccountBtn.isEmpty()) { + AdbUtils::makeTap(device.id, transferToOtherAccountBtn.x(), transferToOtherAccountBtn.y()); + QThread::msleep(1000); + xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_account.deviceId, ++m_counter)); + + Node transferToOtherAccountTitle = xmlScreenParser.findTextNode( + "Transfer to Other Account", 0, 0, device.screenWidth, device.screenHeight / 2 + ); + Node aNewRecipientBtn = xmlScreenParser.findTextNode("A New Recipient"); + if (!transferToOtherAccountTitle.isEmpty() && !aNewRecipientBtn.isEmpty()) { + AdbUtils::makeTap(device.id, aNewRecipientBtn.x(), aNewRecipientBtn.y()); QThread::msleep(1000); - } else { - AdbUtils::takeScreenshot(device.id, "pay_by_phone_not_enough_money_" + QString::number(m_account.id)); - m_error = "Not enough money, id: " + QString::number(m_account.id) - + ", amount: " + QString::number(account.amount) - + ", trx amount: " + QString::number(m_amount); - qCritical() << m_error; - emit finishedWithResult(m_error); - return; - } - break; - } - } - makePayment(device.id, app.pinCode, device.screenWidth, device.screenHeight); - // AdbUtils::tryToKillApplication(device.id, app.package); TODO - надо ли это??? + makePayment(device); + } else { + m_error = "Не нашли кнопку 'A New Recipient'"; + qDebug() << m_error; + } + } else { + m_error = "Не нашли кнопку 'Transfer to Other Account'"; + qDebug() << m_error; + } + } else { + m_error = "Не нашли кнопку 'Money Transfers'"; + qDebug() << m_error; + } } else { - AdbUtils::takeScreenshot(device.id, "pay_by_phone_cant_open_all_products_" + QString::number(m_account.id)); - AdbUtils::tryToKillApplication(device.id, app.package); - m_error = "Не смогли открыть 'Все продукты'"; + m_error = "Не нашли пункт меню 'Transactions'"; qDebug() << m_error; } + if (!m_error.isEmpty()) { + AdbUtils::takeXmlDump(device.id, "pay_by_iban_fail_transactions_" + QString::number(m_account.id)); + } + emit finishedWithResult(m_error); } diff --git a/android/ziraat/PayByIbanScript.h b/android/ziraat/PayByIbanScript.h index 8713ee1..717e3c9 100644 --- a/android/ziraat/PayByIbanScript.h +++ b/android/ziraat/PayByIbanScript.h @@ -1,5 +1,6 @@ #pragma once #include "CommonZiraatScript.h" +#include "db/DeviceInfo.h" class PayByIbanScript final : public CommonZiraatScript { Q_OBJECT @@ -9,8 +10,8 @@ public: PayByIbanScript( AccountInfo account, - QString phone, - QString bankName, + QString iban, + QString fullName, double amount, QObject *parent = nullptr ); @@ -20,15 +21,10 @@ protected: private: const AccountInfo m_account; - const QString m_phone; - const QString m_bankName; + const QString m_iban; + const QString m_fullName; const double m_amount; QString m_error; - void makePayment( - const QString &deviceId, - const QString &pinCode, - int width, - int height - ); + void makePayment(const DeviceInfo &device); }; diff --git a/android/ziraat/ScreenXmlZiraatParser.cpp b/android/ziraat/ScreenXmlZiraatParser.cpp index 4e3caef..026c5ef 100644 --- a/android/ziraat/ScreenXmlZiraatParser.cpp +++ b/android/ziraat/ScreenXmlZiraatParser.cpp @@ -59,6 +59,11 @@ Node ScreenXmlZiraatParser::tryToFindBannerOrDialog(const int width, const int h return {}; } +Node ScreenXmlZiraatParser::findAccountButton(const QString &numbers) { + // TODO возможно это не так, надо уточнять + return findTextNode(QString("LEVENT - %1").arg(numbers)); +} + /** * Смотрим что есть текс "Мы заботимся о вашей безопасности" и * кнопка "Отложить" @@ -117,9 +122,18 @@ Node ScreenXmlZiraatParser::findTextNode(const QString &text, const int x1, cons return {}; } -Node ScreenXmlZiraatParser::findFirstEditText() { +Node ScreenXmlZiraatParser::findEditText(const QString &text) { for (const Node &node: m_nodes) { - if (node.className == "android.widget.EditText" && node.focusable) { + if (node.className == "android.widget.EditText" && node.text == text) { + return node; + } + } + return {}; +} + +Node ScreenXmlZiraatParser::findEditTextByResourceId(const QString &resourceId) { + for (const Node &node: m_nodes) { + if (node.className == "android.widget.EditText" && node.resourceId == resourceId) { return node; } } @@ -142,18 +156,17 @@ Node ScreenXmlZiraatParser::findButtonNode(const QString &text, const bool conta } bool ScreenXmlZiraatParser::isHomeScreen() { - // Есть заголовок "Счета и карты" и кнопка "Все продукты" - Node text; + Node activitiesBtn; Node allProductBtn; for (const Node &node: m_nodes) { - if (node.text.trimmed() == "Счета и карты" && node.className == "android.widget.TextView") { - text = node; + if (node.text.trimmed() == "Account Activities" && node.className == "android.widget.Button") { + activitiesBtn = node; } - if (node.text.trimmed() == "Все продукты" && node.className == "android.widget.Button") { + if (node.text.trimmed() == "All My Accounts" && node.className == "android.widget.Button") { allProductBtn = node; } } - return !text.isEmpty() && !allProductBtn.isEmpty(); + return !activitiesBtn.isEmpty() && !allProductBtn.isEmpty(); } QList ScreenXmlZiraatParser::findPinCodeNodes(const QString &pinCode) { @@ -174,22 +187,6 @@ QList ScreenXmlZiraatParser::findPinCodeNodes(const QString &pinCode) { return nodes; } -QList ScreenXmlZiraatParser::tryToFindPinCode(const QString &pinCode, const QString &title) { - for (const Node &node: m_nodes) { - // если там есть такая кнопка значит банер есть, и пин-код не прожать - if (node.text.trimmed() == "Отложить" && node.className == "android.widget.Button") { - return {}; - } - } - for (const Node &node: m_nodes) { - // FIXME определение пин-кода лучше сделать - if (node.text.trimmed() == title) { - return findPinCodeNodes(pinCode); - } - } - return {}; -} - QList ScreenXmlZiraatParser::getLast2DaysTransactions() { QList nodes; for (const Node &node: m_nodes) { @@ -546,7 +543,8 @@ QDomElement findElementWithListView(const QDomElement &parent) { while (!element.isNull()) { QDomElement child = element.firstChildElement(); while (!child.isNull()) { - if (child.hasAttribute("class") && child.attribute("class") == "android.widget.ListView") { + if (child.hasAttribute("class") && child.attribute("class") == + "androidx.recyclerview.widget.RecyclerView") { return child; } child = child.nextSiblingElement(); @@ -561,62 +559,66 @@ QDomElement findElementWithListView(const QDomElement &parent) { return {}; } + +QDateTime getUtcFromTurkey(const QString &d, const QString &m, const QString &t) { + const QMap monthMap = { + {"JAN", 1}, {"FEB", 2}, {"MAR", 3}, {"APR", 4}, + {"MAY", 5}, {"JUN", 6}, {"JUL", 7}, {"AUG", 8}, + {"SEP", 9}, {"OCT", 10}, {"NOV", 11}, {"DEC", 12} + }; + + const int day = d.toInt(); + const int month = monthMap.value(m.toUpper(), 0); + const int year = QDate::currentDate().year(); + + const QDate date(year, month, day); + const QTime time = QTime::fromString(t, "HH:mm"); + + const QTimeZone tz("Europe/Istanbul"); // TRT (UTC+3, без перехода на зимнее время) + const QDateTime dtLocal(date, time, tz); + + const QDateTime dtUtc = dtLocal.toTimeZone(QTimeZone::utc()); + return dtUtc; +} + + QList ScreenXmlZiraatParser::parseTodayAndYesterdayReportHistory() { QList transactions; QDomElement child = findElementWithListView(m_xml); - QDomNodeList listView = findElementWithListView(m_xml).childNodes(); + QDomNodeList listView = child.childNodes(); qDebug() << listView.count(); if (!listView.isEmpty()) { - QString date; for (QDomNode node: listView) { - if (node.hasChildNodes()) { - QString text; - if (node.childNodes().count() > 1) { - date = node.firstChildElement().attribute("text"); - text = node.lastChildElement().attribute("text"); - } else if (node.childNodes().count() == 1) { - text = node.firstChildElement().attribute("text"); - } - - TransactionInfo transaction; - if (text.startsWith("Перевод")) { - QRegularExpression re(R"([+-] \d{1,3}(?: \d{3})* точка \d{2} Рубль$)"); - QRegularExpressionMatch match = re.match(text); - if (match.hasMatch()) { - QString amount = match.captured(0); - transaction.amount = parseAmount(amount.trimmed()); - text.chop(amount.length()); - } - transaction.description = text.trimmed(); - parseTransferInfo(text, transaction); - if (transaction.bankTime.isEmpty()) { - transaction.bankTime = transformBankDateTime(date); - } - } else { - // карта - if (text.contains("без учета комиссии")) { - QRegularExpression re(R"( без учета комиссии -\d{1,3}(?: \d{3})* точка \d{2} Рубль$)"); - QRegularExpressionMatch match = re.match(text); - if (match.hasMatch()) { - QString fee = match.captured(0); - text.chop(fee.length()); - transaction.fee = parseAmount(fee.replace("без учета комиссии", "").trimmed()); + const QDomNodeList trBoxGroup = node.childNodes(); + if (trBoxGroup.count() > 0) { + for (QDomNode item: trBoxGroup) { + TransactionInfo transaction; + QDomElement groupItem = item.toElement(); + if (groupItem.attribute("index") == "0") { + for (QDomNode subDate: groupItem.childNodes()) { + QDomElement dateItem = subDate.toElement(); + QString date; + QString month; + QString time; + if (dateItem.attribute("index") == "0") { + date = dateItem.attribute("text"); + } else if (groupItem.attribute("index") == "1") { + month = groupItem.attribute("text"); + } else if (groupItem.attribute("index") == "2") { + time = groupItem.attribute("text"); + } + transaction.bankTime = date + " " + month + " " + time; + transaction.completeTime = getUtcFromTurkey(date, month, time); + transaction.timestamp = DateUtils::getUtcNow(); } + } else if (groupItem.attribute("index") == "2") { + transaction.description = groupItem.attribute("text"); + } else if (groupItem.attribute("index") == "3") { + transaction.amount = groupItem.attribute("text").replace("TL", "").trimmed().toFloat(); } - QRegularExpression re(R"([+-] \d{1,3}(?: \d{3})* точка \d{2} Рубль$)"); - QRegularExpressionMatch match = re.match(text); - if (match.hasMatch()) { - QString amount = match.captured(0); - transaction.amount = parseAmount(amount.trimmed()); - text.chop(amount.length()); - } - transaction.description = text.trimmed(); - if (transaction.bankTime.isEmpty()) { - transaction.bankTime = transformBankDateTime(date); - } + transactions.append(transaction); } - transactions.append(transaction); } } } else { @@ -920,29 +922,39 @@ TransactionInfo ScreenXmlZiraatParser::parseTransactionInfo(int screenWidth, int } -QList > ScreenXmlZiraatParser::parseAccountsInfo() { - QList > accounts; - for (const Node &node: m_nodes) { - const QString text = node.text; +QList ScreenXmlZiraatParser::parseAccountsInfo() { + const QDomNodeList nodes = m_xml.elementsByTagName("node"); + QList accounts; - // Проверяем только нужные строки - if (!text.contains("Номер карты с последними цифрами")) { - continue; + for (int i = 0; i < nodes.count(); ++i) { + const QDomElement el = nodes.at(i).toElement(); + if (el.attribute("resource-id") == "com.ziraat.ziraatmobil:id/text_account_or_bank_name" && + el.attribute("text").startsWith("LEVENT") + ) { + // Нашли нужный аккаунт + const QDomElement parent = el.parentNode().toElement(); // account_name_layout + const QDomElement balanceFrame = parent.parentNode().parentNode().lastChildElement("node"); + // Перебираем потомков в поисках суммы + QDomNode n = balanceFrame; + while (!n.isNull()) { + const QDomElement e = n.toElement(); + if (e.attribute("resource-id") == "com.ziraat.ziraatmobil:id/text_value") { + QString amount = e.attribute("text"); + QString account = el.attribute("text"); + + AccountInfo info; + info.lastNumbers = account.split("-").last().trimmed(); + info.amount = amount.toFloat(); + info.description = account; + info.updateTime = DateUtils::getUtcNow(); + + accounts.append(info); + break; + } + n = n.nextSibling(); + } } - QRegularExpressionMatch match = reCardData.match(text); - if (!match.hasMatch()) { - continue; - } - - AccountInfo info; - info.lastNumbers = match.captured(1).remove(" "); - info.amount = match.captured(2).remove(" ").toFloat(); - info.description = text.mid(0, text.indexOf("Номер карты")); - info.updateTime = DateUtils::getUtcNow(); - - accounts.append(qMakePair(info, node)); } - return accounts; } diff --git a/android/ziraat/ScreenXmlZiraatParser.h b/android/ziraat/ScreenXmlZiraatParser.h index 8f64796..3964d7f 100644 --- a/android/ziraat/ScreenXmlZiraatParser.h +++ b/android/ziraat/ScreenXmlZiraatParser.h @@ -20,24 +20,26 @@ public: 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 findFirstEditText(); + Node findEditText(const QString &text); + + Node findEditTextByResourceId(const QString &resourceId); Node findButtonNode(const QString &text, bool contains); bool isHomeScreen(); - - QList tryToFindPinCode(const QString &pinCode, const QString &title); - QList getLast2DaysTransactions(); void parseAndSaveXml(const QString &xml); TransactionInfo parseTransactionInfoHistory(int screenWidth, int screenHeight); + TransactionInfo parseTransactionInfo(int screenWidth, int screenHeight); QList parseTodayAndYesterdayReportHistory(); @@ -45,7 +47,7 @@ public: QList parseTodayAndYesterdayHistory(); - QList > parseAccountsInfo(); + QList parseAccountsInfo(); void test(); diff --git a/config.ini b/config.ini index 6a662e1..eda658f 100644 --- a/config.ini +++ b/config.ini @@ -12,6 +12,10 @@ android_package_name=ru.rshb.dbo name=Альфа-Банк android_package_name=ru.alfabank.mobile.android +[ziraat] +name=Ziraat +android_package_name=com.ziraat.ziraatmobil + [santanderbank] name=Santander android_package_name=ar.com.santander.rio.mbanking diff --git a/database/time/DateUtils.cpp b/database/time/DateUtils.cpp index 15703c7..564313b 100644 --- a/database/time/DateUtils.cpp +++ b/database/time/DateUtils.cpp @@ -22,4 +22,4 @@ QDateTime DateUtils::fromUtcString(const QString ×tamp) { QString DateUtils::toUtcString(const QDateTime &dateTime) { return dateTime.toString("yyyy-MM-dd HH:mm:ss"); -} +} \ No newline at end of file diff --git a/models/db/TransactionInfo.h b/models/db/TransactionInfo.h index ad41ce4..e8272f7 100644 --- a/models/db/TransactionInfo.h +++ b/models/db/TransactionInfo.h @@ -6,6 +6,7 @@ enum class TransactionType { Card, Phone, + Iban, Unknown }; @@ -20,6 +21,7 @@ inline QString transactionTypeToString(const TransactionType type) { switch (type) { case TransactionType::Card: return "card"; case TransactionType::Phone: return "phone"; + case TransactionType::Iban: return "iban"; default: return "unknown"; } } @@ -27,6 +29,7 @@ inline QString transactionTypeToString(const TransactionType type) { inline TransactionType transactionTypeFromString(const QString &str) { if (str == "card") return TransactionType::Card; if (str == "phone") return TransactionType::Phone; + if (str == "iban") return TransactionType::Iban; return TransactionType::Unknown; }