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; }