1
0
forked from BRT/arc

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.
This commit is contained in:
slava 2025-06-17 18:13:29 +07:00
parent ab16b8962f
commit 2552ca49be
13 changed files with 709 additions and 470 deletions

View File

@ -35,17 +35,6 @@ Node CommonZiraatScript::swipeToButton(
return {};
}
bool CommonZiraatScript::inputPinCode(const QString &deviceId, const QList<Node> &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,124 +69,66 @@ 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<Node> pincode = xmlScreenParser.tryToFindPinCode(pinCode, "Введите ПИН");
pincode.size() == 4) {
// чтобы пробудить экран
if (Node textPinCodeNode = xmlScreenParser.findTextNode("Введите ПИН"); !textPinCodeNode.isEmpty()) {
AdbUtils::makeTap(deviceId, textPinCodeNode.x(), textPinCodeNode.y());
}
inputPinCode(deviceId, pincode);
// После ввода пин-код нужно сделать задержку, пока подгрузка идет, чтобы снова не спарсить
findAndInputPincode = true;
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;
}
}
}
QThread::msleep(1000);
}
}
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<QPair<AccountInfo, Node> > 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<QPair<AccountInfo, Node> > &accounts) {
void CommonZiraatScript::postAccountsData(const QList<AccountInfo> &accounts) {
if (accounts.isEmpty()) {
return;
}
@ -206,9 +137,8 @@ void CommonZiraatScript::postAccountsData(const QList<QPair<AccountInfo, Node> >
auto *worker = new NetworkService;
QJsonArray list;
for (const QPair<AccountInfo, Node> &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<QPair<AccountInfo, Node> >
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);

View File

@ -14,17 +14,13 @@ public:
protected:
virtual void doStart() = 0;
static bool inputPinCode(const QString &deviceId, const QList<Node> &pincode);
bool goToAllProducts(
const QString &deviceId,
int width,
int height
);
bool findAndTapOnAccount(const QString &deviceId, const QString &accountNumbers);
void postAccountsData(const QList<QPair<AccountInfo, Node> > &accounts);
void postAccountsData(const QList<AccountInfo> &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;

View File

@ -0,0 +1,160 @@
#include "GetLastDaysHistoryZiraatScript.h"
#include <QJsonObject>
#include <QRandomGenerator>
#include <QThread>
#include <utility>
#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<TransactionInfo> localTransactions = TransactionDAO::getTransactionsWithinHours(m_account.id, 48);
QList<AccountInfo> 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<TransactionInfo> transactions = xmlScreenParser.parseTodayAndYesterdayReportHistory();
if (!transactions.isEmpty()) {
break;
}
}
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_account.deviceId, ++m_counter));
// все подтвержденные
QList<TransactionInfo> parsedTransactions = xmlScreenParser.parseTodayAndYesterdayReportHistory();
QSet<int> 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);
}

View File

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

View File

@ -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<AccountInfo> 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);
}

View File

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

View File

@ -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(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());
QThread::msleep(1000);
} else {
AdbUtils::takeScreenshot(deviceId, "pay_by_phone_not_found_pay_by_phone");
m_error = "Не смогли найти пункт 'По номеру телефона В РСХБ и через'";
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());
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(deviceId, m_phone);
QThread::msleep(1000);
} else {
AdbUtils::takeScreenshot(deviceId, "pay_by_phone_cant_find_pay_by_phone");
m_error = "Не смогли найти пункт 'Перевод по телефону'";
qDebug() << m_error;
return;
}
AdbUtils::inputText(device.id, m_iban);
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;
}
AdbUtils::makeTap(device.id, editIbanEditText.x(), editIbanEditText.y());
QThread::msleep(500);
AdbUtils::inputText(device.id, m_fullName);
if (m_bankName != QStringLiteral("Россельхозбанк")) {
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
Node nextBtn = xmlScreenParser.findButtonNode("NEXT", false);
AdbUtils::makeTap(device.id, nextBtn.x(), nextBtn.y());
bool findSenderAccount = 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));
}
}
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
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));
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);
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);
}
AdbUtils::makeTap(deviceId, continuePayBtn.x(), continuePayBtn.y());
QThread::msleep(1000);
} else {
AdbUtils::takeScreenshot(deviceId, "pay_by_phone_cant_tap_on_ready");
m_error = "Не смогли нажать 'Готово'";
qDebug() << m_error;
return;
}
} else {
AdbUtils::takeScreenshot(deviceId, "pay_by_phone_cant_tap_on_continue");
m_error = "Не смогли нажать 'Продолжить'";
qDebug() << m_error;
return;
}
} else {
AdbUtils::takeScreenshot(deviceId, "pay_by_phone_cant_find_bank_title");
m_error = "Не смогли найти заголовок: " + payByPhoneScreenTitleText;
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<Node> pincode = xmlScreenParser.tryToFindPinCode(pinCode, "Введите код доступа");
pincode.size() == 4) {
inputPinCode(deviceId, pincode);
inputedPinCode = true;
// После ввода пин-кода, ждем 3с
QThread::msleep(3000);
// FXIME скорее всего придется нажать кнопку NEXT
findSenderAccount = true;
}
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 = "Не смогли ввести пин-код!'";
if (!findSenderAccount) {
m_error = "Не нашли Sender Account";
qDebug() << m_error;
return;
}
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
bool findAmountDetermination = false;
for (int i = 0; i < 5; ++i) {
QThread::msleep(1000);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++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));
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<int>(m_amount);
AdbUtils::makeTap(device.id, integerEditText.x(), integerEditText.y());
QThread::msleep(500);
AdbUtils::inputText(device.id, QString::number(integerPart));
int decimalPart = static_cast<int>((m_amount - integerPart) * 100);
AdbUtils::makeTap(device.id, decimalEditText.x(), decimalEditText.y());
QThread::msleep(500);
AdbUtils::inputText(device.id, QString::number(decimalPart));
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 = xmlScreenParser.parseTransactionInfo(width, height);
if (isRshbBank) {
transaction.bankName = m_bankName;
}
TransactionInfo transaction;
transaction.name = m_fullName;
transaction.accountId = m_account.id;
transaction.amount = -m_amount;
transaction.type = TransactionType::Phone;
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...";
}
// ждем 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 backBtn = xmlScreenParser.findButtonNode("BACK", false);
if (!backBtn.isEmpty()) {
AdbUtils::makeTap(device.id, backBtn.x(), backBtn.y());
QThread::msleep(500);
Node homeBtn = xmlScreenParser.findButtonNode("Вернуться на главную", false);
AdbUtils::makeTap(deviceId, homeBtn.x(), homeBtn.y());
QThread::msleep(1000);
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;
}
}
} else {
m_error = "Транзакция не успешна";
qDebug() << m_error;
return;
}
} else {
m_error = "Не нашли Check the details title info";
qDebug() << m_error;
return;
}
} else {
m_error = "Не нашли поля ввода для IBAN";
qDebug() << m_error;
return;
}
}
void PayByIbanScript::doStart() {
@ -291,46 +288,62 @@ void PayByIbanScript::doStart() {
}
}
if (goToAllProducts(device.id, device.screenWidth, device.screenHeight)) {
QList<QPair<AccountInfo, Node> > 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;
}
}
postAccountsData(accounts);
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());
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);
} 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;
}
}
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_account.deviceId, ++m_counter));
makePayment(device.id, app.pinCode, device.screenWidth, device.screenHeight);
// AdbUtils::tryToKillApplication(device.id, app.package); TODO - надо ли это???
// перешли на страницу транзакций
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));
// перешли на страницу переводов
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);
makePayment(device);
} 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 = "Не нашли кнопку '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 {
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);
}

View File

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

View File

@ -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<Node> ScreenXmlZiraatParser::findPinCodeNodes(const QString &pinCode) {
@ -174,22 +187,6 @@ QList<Node> ScreenXmlZiraatParser::findPinCodeNodes(const QString &pinCode) {
return nodes;
}
QList<Node> 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<Node> ScreenXmlZiraatParser::getLast2DaysTransactions() {
QList<Node> 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,64 +559,68 @@ QDomElement findElementWithListView(const QDomElement &parent) {
return {};
}
QDateTime getUtcFromTurkey(const QString &d, const QString &m, const QString &t) {
const QMap<QString, int> 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<TransactionInfo> ScreenXmlZiraatParser::parseTodayAndYesterdayReportHistory() {
QList<TransactionInfo> 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");
}
const QDomNodeList trBoxGroup = node.childNodes();
if (trBoxGroup.count() > 0) {
for (QDomNode item: trBoxGroup) {
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());
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.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());
}
}
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);
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();
}
transactions.append(transaction);
}
}
}
} else {
qDebug() << "ListView is empty";
}
@ -920,29 +922,39 @@ TransactionInfo ScreenXmlZiraatParser::parseTransactionInfo(int screenWidth, int
}
QList<QPair<AccountInfo, Node> > ScreenXmlZiraatParser::parseAccountsInfo() {
QList<QPair<AccountInfo, Node> > accounts;
for (const Node &node: m_nodes) {
const QString text = node.text;
QList<AccountInfo> ScreenXmlZiraatParser::parseAccountsInfo() {
const QDomNodeList nodes = m_xml.elementsByTagName("node");
QList<AccountInfo> accounts;
// Проверяем только нужные строки
if (!text.contains("Номер карты с последними цифрами")) {
continue;
}
QRegularExpressionMatch match = reCardData.match(text);
if (!match.hasMatch()) {
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 = match.captured(1).remove(" ");
info.amount = match.captured(2).remove(" ").toFloat();
info.description = text.mid(0, text.indexOf("Номер карты"));
info.lastNumbers = account.split("-").last().trimmed();
info.amount = amount.toFloat();
info.description = account;
info.updateTime = DateUtils::getUtcNow();
accounts.append(qMakePair(info, node));
accounts.append(info);
break;
}
n = n.nextSibling();
}
}
}
return accounts;
}

View File

@ -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<Node> tryToFindPinCode(const QString &pinCode, const QString &title);
QList<Node> getLast2DaysTransactions();
void parseAndSaveXml(const QString &xml);
TransactionInfo parseTransactionInfoHistory(int screenWidth, int screenHeight);
TransactionInfo parseTransactionInfo(int screenWidth, int screenHeight);
QList<TransactionInfo> parseTodayAndYesterdayReportHistory();
@ -45,7 +47,7 @@ public:
QList<TransactionInfo> parseTodayAndYesterdayHistory();
QList<QPair<AccountInfo, Node> > parseAccountsInfo();
QList<AccountInfo> parseAccountsInfo();
void test();

View File

@ -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

View File

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