1
0
forked from BRT/arc
arc/android/ziraat/ScreenXmlZiraatParser.cpp
slava 2552ca49be 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.
2025-06-17 18:13:29 +07:00

989 lines
38 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "ScreenXmlZiraatParser.h"
#include <QRegularExpression>
#include <QMap>
#include <QSet>
#include <QList>
#include <QXmlStreamReader>
#include <QDomDocument>
#include <QFile>
#include <QDateTime>
#include "adb/AdbUtils.h"
#include "android/xml/Node.h"
#include "db/AccountInfo.h"
#include "db/TransactionInfo.h"
#include "time/DateUtils.h"
ScreenXmlZiraatParser::ScreenXmlZiraatParser(QObject *parent) : QObject(parent) {
}
ScreenXmlZiraatParser::~ScreenXmlZiraatParser() = default;
static const QRegularExpression boundsRegex(R"(\[([0-9]+),([0-9]+)\]\[([0-9]+),([0-9]+)\])");
static const QRegularExpression onlyDigitsRegex(R"([^0-9])");
static const QRegularExpression onlyDigitsAndDotRegex(R"([^0-9.])");
static const QRegularExpression rePhone(R"raw((\+7\(?\d{3}\)?[- ]?\d{3}[- ]?\d{2}[- ]?\d{2}))raw");
static const QRegularExpression reFromBank(R"raw(из (.+?) на)raw");
static const QRegularExpression reToBank(R"raw(в (.+?) через)raw");
static const QRegularExpression reSbpId(R"(СБП(?: С2С)? ([A-Z0-9]{30,}))");
static const QRegularExpression reIdAlt(R"(ID перевода (\d+))");
static const QRegularExpression reDate(R"((?:Дата операции|дата) (\d{2}\.\d{2}\.\d{4}(?: \d{2}:\d{2}:\d{2})?))");
static const QRegularExpression reSender(R"(от ([^\(]+))");
static const QRegularExpression reNameAlt(R"(Перевод ([А-Яа-яЁё\s\*]+) в)");
static const QRegularExpression reCardData(R"(цифрами\s+(\d\s\d\s\d\s\d).*?Сумма:\s([\d\s]+)\sруб)");
Node ScreenXmlZiraatParser::tryToFindBannerOrDialog(const int width, const int height) {
// 1. Ищем push banner - он перекрывает весь экран
Node first = m_nodes[0];
for (const Node &node: m_nodes) {
// Если это однозначно, что пуш-баннер
if (node.resourceId == "aft-popup-pushNotification-iconClose" && node.className == "android.widget.Button") {
return node;
}
// Если есть кнопка "Позже"
if (node.text.trimmed() == "Позже" && node.className == "android.widget.Button") {
return node;
}
// Если нашли пустую кнпоку
if (node.className == "android.widget.Button" && node.clickable
&& node.text.isEmpty() && node.contentDesc.isEmpty()) {
// <node class="android.widget.Button" package="ru.rshb.dbo" clickable="true" bounds="[630,142][691,205]"/>
if (node.x() > width / 2 && node.y() < width / 2) {
return node;
}
}
}
return {};
}
Node ScreenXmlZiraatParser::findAccountButton(const QString &numbers) {
// TODO возможно это не так, надо уточнять
return findTextNode(QString("LEVENT - %1").arg(numbers));
}
/**
* Смотрим что есть текс "Мы заботимся о вашей безопасности" и
* кнопка "Отложить"
* @return координаты кнопки для закрытия
*/
Node ScreenXmlZiraatParser::tryToFindSecurityBanner() {
Node text;
Node closeBtn;
for (const Node &node: m_nodes) {
if (node.text.trimmed() == "Мы заботимся о вашей безопасности" && node.className == "android.app.Dialog") {
text = node;
}
if (node.text.trimmed() == "Отложить" && node.className == "android.widget.Button") {
closeBtn = node;
}
}
// Не нужно проверять все, если хотя бы одна координата есть
if (!text.isEmpty() && !closeBtn.isEmpty()) {
return closeBtn;
}
return {};
}
Node ScreenXmlZiraatParser::tryToFindSelectBottomSheet() {
Node closeBtn;
for (const Node &node: m_nodes) {
if ((node.text.trimmed() == "Отмена" || node.text.trimmed() == "Отложить") && node.className ==
"android.widget.Button") {
closeBtn = node;
}
}
// Не нужно проверять все, если хотя бы одна координата есть
if (!closeBtn.isEmpty()) {
return closeBtn;
}
return {};
}
Node ScreenXmlZiraatParser::findTextNode(const QString &text) {
for (const Node &node: m_nodes) {
if (node.className == "android.widget.TextView" && node.text.trimmed() == text) {
return node;
}
}
return {};
}
Node ScreenXmlZiraatParser::findTextNode(const QString &text, const int x1, const int y1, const int x2, const int y2) {
for (const Node &node: m_nodes) {
if (node.className == "android.widget.TextView" && node.text.trimmed() == text
&& node.x() >= x1 && node.x() <= x2 && node.y() >= y1 && node.y() <= y2) {
return node;
}
}
return {};
}
Node ScreenXmlZiraatParser::findEditText(const QString &text) {
for (const Node &node: m_nodes) {
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;
}
}
return {};
}
Node ScreenXmlZiraatParser::findButtonNode(const QString &text, const bool contains) {
for (const Node &node: m_nodes) {
if (contains) {
if (node.className == "android.widget.Button" && node.text.contains(text)) {
return node;
}
} else {
if (node.className == "android.widget.Button" && node.text.trimmed() == text) {
return node;
}
}
}
return {};
}
bool ScreenXmlZiraatParser::isHomeScreen() {
Node activitiesBtn;
Node allProductBtn;
for (const Node &node: m_nodes) {
if (node.text.trimmed() == "Account Activities" && node.className == "android.widget.Button") {
activitiesBtn = node;
}
if (node.text.trimmed() == "All My Accounts" && node.className == "android.widget.Button") {
allProductBtn = node;
}
}
return !activitiesBtn.isEmpty() && !allProductBtn.isEmpty();
}
QList<Node> ScreenXmlZiraatParser::findPinCodeNodes(const QString &pinCode) {
QMap<QString, Node> buttons;
const QSet<QString> numberSet = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"};
for (const Node &node: m_nodes) {
const QString &text = node.text.trimmed();
if (node.className == "android.widget.Button" && numberSet.contains(text)) {
buttons[text] = node;
}
}
QList<Node> nodes;
for (int i = 0; i < pinCode.length(); ++i) {
nodes.append(buttons[QString(pinCode.at(i))]);
}
return nodes;
}
QList<Node> ScreenXmlZiraatParser::getLast2DaysTransactions() {
QList<Node> nodes;
for (const Node &node: m_nodes) {
if (node.className == "android.widget.Button" && node.text.contains("рубл")
&& (node.text.contains("Минус") || node.text.contains("Плюс"))) {
nodes.append(node);
}
}
return nodes;
}
/**
* <node NAF="true" index="1" text="" resource-id=""
class="android.widget.Button" package="ru.rshb.dbo"
content-desc="" checkable="false" checked="false"
clickable="true" enabled="true" focusable="false"
focused="false" scrollable="false"
long-clickable="false" password="false"
selected="false" bounds="[952,159][1039,248]"/>
*/
void ScreenXmlZiraatParser::parseAndSaveXml(const QString &xml) {
QDomDocument doc;
doc.setContent(xml);
m_xml = doc.documentElement();
m_nodes.clear();
QXmlStreamReader reader(xml);
while (!reader.atEnd()) {
reader.readNext();
if (reader.isStartElement() && reader.name() == "node") {
Node node = Node();
if (reader.attributes().hasAttribute("bounds")) {
QString bounds = reader.attributes().value("bounds").toString();
if (QRegularExpressionMatch match = boundsRegex.match(bounds); match.hasMatch()) {
node.setBounds(
match.captured(1).toInt(),
match.captured(2).toInt(),
match.captured(3).toInt(),
match.captured(4).toInt()
);
}
}
if (reader.attributes().hasAttribute("text")) {
node.text = reader.attributes().value("text").toString();
}
if (reader.attributes().hasAttribute("content-desc")) {
node.contentDesc = reader.attributes().value("content-desc").toString();
}
if (reader.attributes().hasAttribute("class")) {
node.className = reader.attributes().value("class").toString();
}
if (reader.attributes().hasAttribute("clickable")) {
node.clickable = reader.attributes().value("clickable").toString() == "true";
}
if (reader.attributes().hasAttribute("focusable")) {
node.focusable = reader.attributes().value("focusable").toString() == "true";
}
if (reader.attributes().hasAttribute("resource-id")) {
node.resourceId = reader.attributes().value("resource-id").toString();
}
m_nodes.append(node);
}
}
}
QString getDayOfWeekAsString(const int day_of_week) {
switch (day_of_week) {
case 1: return "понедельник";
case 2: return "вторник";
case 3: return "среда";
case 4: return "четверг";
case 5: return "пятница";
case 6: return "суббота";
case 7: return "воскресенье";
default: return "";
}
}
QString getMonthAsString(const int month) {
switch (month) {
case 1: return "января";
case 2: return "февраля";
case 3: return "марта";
case 4: return "апреля";
case 5: return "мая";
case 6: return "июня";
case 7: return "июля";
case 8: return "августа";
case 9: return "сентября";
case 10: return "октября";
case 11: return "ноября";
case 12: return "декабря";
default: return "";
}
}
QDomElement findSubElement(const QDomElement &elem, const QString &index) {
if (elem.hasChildNodes()) {
QDomNodeList subInfoList = elem.childNodes();
for (QDomNode subInfoNode: subInfoList) {
if (!subInfoNode.isElement()) continue;
QDomElement subInfo = subInfoNode.toElement();
if (subInfo.attribute("index") == index) {
return subInfo;
}
}
}
return {};
}
QString findSubInfo(const QDomElement &elem, const QString &index) {
QDomElement subInfo = findSubElement(elem, index);
if (!subInfo.isNull()) {
return subInfo.attribute("text");
}
return "";
}
bool hasTimeComponent(const QString &datetimeStr) {
QDateTime dt = QDateTime::fromString(datetimeStr, "dd.MM.yyyy HH:mm:ss");
if (!dt.isValid()) {
return false; // Строка не соответствует формату даты и времени
}
return dt.time() != QTime(0, 0, 0); // Проверка, что время не равно 00:00:00
}
QString transformBankDateTime(const QString &date) {
QDateTime dt;
if (date.contains(",")) {
// 10.05.2025, 15:03
dt = QDateTime::fromString(date, "dd.MM.yyyy, HH:mm");
} else {
dt = QDateTime::fromString(date, "dd.MM.yyyy HH:mm:ss");
}
if (!dt.isValid()) {
dt = QDateTime::fromString(date, "dd.MM.yyyy HH:mm");
}
if (!dt.isValid()) {
return date; // не распарсили
}
return dt.toString("dd.MM.yyyy HH:mm:ss");
}
void parseTransferInfo(const QString &text, TransactionInfo &info) {
if (const auto m = reSender.match(text); m.hasMatch()) {
info.name = m.captured(1).trimmed();
} else if (const auto m1 = reNameAlt.match(text); m1.hasMatch()) {
info.name = m1.captured(1).trimmed();
}
if (const auto m = rePhone.match(text); m.hasMatch()) {
info.phone = m.captured(1).trimmed().remove(onlyDigitsRegex);
}
if (const auto m = reFromBank.match(text); m.hasMatch()) {
if (const QString bank = m.captured(1).trimmed(); !bank.isEmpty()) {
info.bankName = bank;
}
} else if (const auto m1 = reToBank.match(text); m1.hasMatch()) {
if (const QString bank = m1.captured(1).trimmed(); !bank.isEmpty()) {
info.bankName = bank;
}
}
if (const auto m = reSbpId.match(text); m.hasMatch()) {
info.bankTrExternalId = m.captured(1);
} else if (const auto m1 = reIdAlt.match(text); m1.hasMatch()) {
info.bankTrExternalId = m1.captured(1);
}
if (const auto m = reDate.match(text); m.hasMatch()) {
const QString date = m.captured(1);
// если не пустая и дата у нас с 00:00
// if (hasTimeComponent(date)) {
info.bankTime = transformBankDateTime(date);
// }
}
}
void traverseNodes(const QDomNode &node) {
if (node.isElement()) {
QDomElement elem = node.toElement();
if (node.isElement()) {
QDomElement elem = node.toElement();
if (elem.tagName() == "node" &&
elem.attribute("class") == "android.widget.Button" &&
elem.attribute("text") == "Детали операции") {
// Все что ниже деталей операции
QDomNodeList detailInfo = elem.parentNode().childNodes();
for (int j = 0; j < detailInfo.count(); ++j) {
QDomNode info = detailInfo.at(j);
if (!info.isElement()) continue;
QDomElement infoElement = info.toElement();
QString index = infoElement.attribute("index");
switch (index.toInt()) {
case 2:
qDebug() << "Дата операции:" << findSubInfo(infoElement, "1");
break;
case 5:
qDebug() << "Название:" << findSubInfo(infoElement, "1");
qDebug() << "-------------------------------------";
// parseTransfer(findSubInfo(infoElement));
break;
case 7:
qDebug() << "Комиссия:" << findSubInfo(infoElement, "1");
break;
default:
qDebug() << "Прочее:" << findSubInfo(infoElement, "1");
break;
}
/**
Node: "2" ""
... Node: "0" "Дата операции"
... Node: "1" "30.04.2025 17:46:29"
Node: "5" ""
... Node: "0" "Название"
... Node: "1" "DBO TRANSFER RSHB INTERNET-BANK"
Node: "7" ""
... Node: "0" "Комиссия"
... Node: "1" "50 ₽"
*/
// if (sibElem.hasChildNodes()) {
// QDomNodeList sibChildren = sibElem.childNodes();
// for (int k = 0; k < sibChildren.count(); ++k) {
// QDomNode sibChild = sibChildren.at(k);
// if (!sibChild.isElement()) continue;
// QDomElement sibChildElem = sibChild.toElement();
// qDebug() << "... Node:"
// << sibChildElem.attribute("index")
// << sibChildElem.attribute("text");
// // << sibChildElem.attribute("class");
//
//
// if (sibChildElem.hasChildNodes()) {
// QDomNodeList sibChildren2 = sibChildElem.childNodes();
// for (int k = 0; k < sibChildren2.count(); ++k) {
// QDomNode sibChild2 = sibChildren2.at(k);
// if (!sibChild2.isElement()) continue;
// QDomElement sibChildElem2 = sibChild2.toElement();
//
// qDebug() << "...... Node:"
// << sibChildElem2.attribute("index")
// << sibChildElem2.attribute("text");
// // << sibChildElem2.attribute("class");
//
// if (sibChildElem2.hasChildNodes()) {
// QDomNodeList sibChildren3 = sibChildElem2.childNodes();
// for (int k = 0; k < sibChildren3.count(); ++k) {
// QDomNode sibChild3 = sibChildren3.at(k);
// if (!sibChild3.isElement()) continue;
// QDomElement sibChildElem3 = sibChild3.toElement();
//
// qDebug() << "......... Node:"
// << sibChildElem3.attribute("index")
// << sibChildElem3.attribute("text");
// // << sibChildElem2.attribute("class");
// }
// }
// }
// }
// }
// }
}
}
}
}
// Рекурсивно обходим всех детей
QDomNode child = node.firstChild();
while (!child.isNull()) {
traverseNodes(child);
child = child.nextSibling();
}
}
QDomNode findNode(const QDomNode &node, const QString &text) {
if (node.isElement()) {
QString nodeText = node.toElement().attribute("text");
if (nodeText.toLower().startsWith(text)) {
return node;
}
}
QDomNode child = node.firstChild();
while (!child.isNull()) {
QDomNode result = findNode(child, text);
if (!result.isNull()) {
return result;
}
child = child.nextSibling();
}
return {};
}
float extractAmounts(const QString &text) {
// Разрешаем пробелы внутри чисел, захватываем "рубль", "рубля", "рублей" и т.д.
// QRegularExpression re(R"((Плюс|Минус)\s+([\d\s]+)\s+рубл\w*\s+(\d+)\s+копе\w*)");
QRegularExpression re(
R"((Плюс|Минус)\s+([\d\s]+)\s+рубл\w*\s+(\d+)\s+копе\w*)",
QRegularExpression::UseUnicodePropertiesOption
);
QRegularExpressionMatch match = re.match(text);
if (match.hasMatch()) {
QString signStr = match.captured(1); // "Плюс" или "Минус"
QString rublesStr = match.captured(2).remove(' '); // "1 000" → "1000"
int rubles = rublesStr.toInt();
int kopecks = match.captured(3).toInt();
int totalKopecks = rubles * 100 + kopecks;
if (signStr == "Минус") {
totalKopecks *= -1;
}
return totalKopecks / 100.0f;
}
return 0;
}
QString beforeAmount(const QString &text) {
int index = text.indexOf(QRegularExpression("Плюс|Минус"));
if (index != -1) {
return text.left(index).trimmed();
}
return text; // если не найдено — вернуть всю строку
}
double parseAmount(const QString &amountAsString) {
QString numberString = amountAsString;
numberString.remove(QRegularExpression(R"([^\очка])"));
numberString.replace("точка", ".");
double amount = numberString.toDouble();
if (amountAsString.startsWith("-")) {
amount *= -1;
}
return amount;
}
QDomElement findElementWithListView(const QDomElement &parent) {
QDomElement element = parent.firstChildElement();
while (!element.isNull()) {
QDomElement child = element.firstChildElement();
while (!child.isNull()) {
if (child.hasAttribute("class") && child.attribute("class") ==
"androidx.recyclerview.widget.RecyclerView") {
return child;
}
child = child.nextSiblingElement();
}
QDomElement found = findElementWithListView(element);
if (!found.isNull()) {
return found;
}
element = element.nextSiblingElement();
}
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 = child.childNodes();
qDebug() << listView.count();
if (!listView.isEmpty()) {
for (QDomNode node: listView) {
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();
}
transactions.append(transaction);
}
}
}
} else {
qDebug() << "ListView is empty";
}
return transactions;
}
QList<TransactionInfo> ScreenXmlZiraatParser::parseTodayAndYesterdayHistory() {
QDate today = DateUtils::getMoscowNow().date();
QDate yesterday = today.addDays(-1);
QDate beforeYesterday = today.addDays(-2);
QString todayTitle = QString("сегодня, %1").arg(getDayOfWeekAsString(today.dayOfWeek()));
QString yesterdayTitle = QString("вчера, %1").arg(getDayOfWeekAsString(yesterday.dayOfWeek()));
QString beforeYesterdayTitle = QString("%1 %2, %3").arg(
QString::number(beforeYesterday.day()),
getMonthAsString(beforeYesterday.month()),
getDayOfWeekAsString(beforeYesterday.dayOfWeek())
);
QDomNode todayNode = findNode(m_xml, todayTitle);
QDomNode yesterdayNode = findNode(m_xml, yesterdayTitle);
QDomNode parentNode;
if (!yesterdayNode.isNull()) {
parentNode = yesterdayNode.parentNode();
} else if (!todayNode.isNull()) {
parentNode = todayNode.parentNode();
}
QList<TransactionInfo> transactions;
if (!parentNode.isNull()) {
QDate date = {};
for (QDomNode transactionNode: parentNode.childNodes()) {
QString transactionShortText = transactionNode.toElement().attribute("text");
const QString titleLowerCase = transactionShortText.toLower();
if (titleLowerCase.startsWith(beforeYesterdayTitle)) {
// только сегодня и вчера парсим, позавчера не трогаем
break;
}
if (titleLowerCase.startsWith(todayTitle)) {
date = today;
transactionShortText = transactionShortText.mid(todayTitle.length() + 1);
}
if (titleLowerCase.startsWith(yesterdayTitle)) {
date = yesterday;
transactionShortText = transactionShortText.mid(yesterdayTitle.length() + 1);
}
if (transactionShortText.contains("ЕСПП")) {
continue;
}
float amount = extractAmounts(transactionShortText);
QString shortDesc = beforeAmount(transactionShortText);
TransactionInfo info;
info.amount = amount;
info.bankTime = date.toString("dd.MM.yyyy");
info.description = shortDesc;
transactions.append(info);
}
}
return transactions;
}
void parse2daysHistory(const QDomNode &node) {
QDate today = DateUtils::getMoscowNow().date();
QDate tomorrow = today.addDays(-1);
QDate after2Days = today.addDays(-2);
QString todayTitle = QString("сегодня, %1").arg(getDayOfWeekAsString(today.dayOfWeek()));
QDomNode todayNode = findNode(node, todayTitle);
if (todayNode.isNull()) {
} else {
qDebug() << "todayNode: " << todayNode.toElement().attribute("text");
}
QString tomorrowTitle = QString("вчера, %1").arg(getDayOfWeekAsString(tomorrow.dayOfWeek()));
QDomNode tomorrowNode = findNode(node, tomorrowTitle);
if (tomorrowNode.isNull()) {
} else {
qDebug() << "tomorrowNode: " << tomorrowNode.toElement().attribute("text");
}
// 30 апреля, Среда
QString after2DaysTitle = QString("%1 %2, %3").arg(
QString::number(after2Days.day()),
getMonthAsString(after2Days.month()),
getDayOfWeekAsString(after2Days.dayOfWeek()));
QDomNode after2DaysNode = findNode(node, after2DaysTitle);
if (after2DaysNode.isNull()) {
qDebug() << "No after2DaysNode";
} else {
qDebug() << "after2DaysNode: " << after2DaysNode.toElement().attribute("text");
}
QDomNode parentNode;
if (!todayNode.isNull() || !tomorrowNode.isNull() || !after2DaysNode.isNull()) {
if (!todayNode.isNull()) {
parentNode = todayNode.parentNode();
}
if (!tomorrowNode.isNull()) {
parentNode = tomorrowNode.parentNode();
}
if (!after2DaysNode.isNull()) {
parentNode = after2DaysNode.parentNode();
}
}
if (!parentNode.isNull()) {
for (QDomNode child_node: parentNode.childNodes()) {
QString child_text = child_node.toElement().attribute("text");
if (child_text.toLower().startsWith(todayTitle)) {
qDebug() << "=== Today: " << todayTitle;
qDebug() << "node: " << beforeAmount(child_text.mid(todayTitle.length() + 1));
qDebug() << "--> sum: " << QString::number(extractAmounts(child_text), 'f', 2);
// qDebug() << "";
continue;
}
if (child_text.toLower().startsWith(tomorrowTitle)) {
qDebug() << "=== Yesterday: " << todayTitle;
qDebug() << "node: " << beforeAmount(child_text.mid(tomorrowTitle.length() + 1));
qDebug() << "--> sum: " << QString::number(extractAmounts(child_text), 'f', 2);
// qDebug() << "";
continue;
}
if (child_text.toLower().startsWith(after2DaysTitle)) {
qDebug() << "=== Before yesterday: " << todayTitle;
qDebug() << "node: " << beforeAmount(child_text.mid(after2DaysTitle.length() + 1));
qDebug() << "--> sum: " << QString::number(extractAmounts(child_text), 'f', 2);
// qDebug() << "";
continue;
}
qDebug() << "node: " << beforeAmount(child_node.toElement().attribute("text"));
qDebug() << "--> sum: " <<
QString::number(extractAmounts(child_node.toElement().attribute("text")), 'f', 2);
// qDebug() << "";
}
}
// QString d = today.day() > 9 ? QString::number(today.day()) : "0" + QString::number(today.day());
// QString m = today.month() > 9 ? QString::number(today.month()) : "0" + QString::number(today.month());
// QString y = QString::number(today.year());
// QString w = getDayOfWeekAsString(today.dayOfWeek());
// QString mm = getMonthAsString(today.month());
// qDebug() << "Date:" << d << m << y << w << mm;
// QString d2 = after2Days.day() > 9 ? QString::number(after2Days.day()) : "0" + QString::number(after2Days.day());
// QString m2 = after2Days.month() > 9
// ? QString::number(after2Days.month())
// : "0" + QString::number(after2Days.month());
// QString y2 = QString::number(after2Days.year());
// QString w2 = getDayOfWeekAsString(after2Days.dayOfWeek());
// QString mm2 = getMonthAsString(after2Days.month());
// qDebug() << "Date:" << d2 << m2 << y2 << w2 << mm2;
}
TransactionInfo parseExpandedInfoHistory(const QDomElement &root) {
if (root.isElement()) {
QDomElement elem = root.toElement();
if (elem.tagName() == "node"
&& elem.attribute("class") == "android.widget.Button"
&& elem.attribute("text") == "Детали операции"
) {
TransactionInfo transaction;
// Все что ниже деталей операции
QDomNodeList detailInfo = elem.parentNode().childNodes();
for (int i = 0; i < detailInfo.count(); ++i) {
QDomNode info = detailInfo.at(i);
if (!info.isElement()) continue;
switch (QDomElement e = info.toElement(); e.attribute("index").toInt()) {
case 2:
// Дата операции
transaction.bankTime = transformBankDateTime(findSubInfo(e, "1"));
break;
case 5:
transaction.description = findSubInfo(e, "1");
parseTransferInfo(transaction.description, transaction);
break;
case 7:
// Комиссия
transaction.fee = findSubInfo(e, "1").remove(onlyDigitsRegex).toFloat();
break;
default:
// qDebug() << "Прочее:" << findSubInfo(e);
break;
}
}
return transaction;
}
}
// Рекурсивно обходим всех детей
QDomNode child = root.firstChild();
while (!child.isNull()) {
if (TransactionInfo info = parseExpandedInfoHistory(child.toElement()); !info.bankTime.isEmpty()) {
return info;
}
child = child.nextSibling();
}
return {};
}
TransactionInfo ScreenXmlZiraatParser::parseTransactionInfoHistory(int screenWidth, int screenHeight) {
// 1. Распарсить описание транзакции
TransactionInfo transaction = parseExpandedInfoHistory(m_xml);
// 1. Найти статус: Исполнен | Операция исполнена
transaction.status = TransactionStatus::Wait;
Node status1 = findTextNode("Исполнен", 0, 0, screenWidth, screenHeight / 2);
Node status2 = findTextNode("Операция исполнена", 0, 0, screenWidth, screenHeight / 2);
if (!status1.isEmpty() || !status2.isEmpty()) {
transaction.status = TransactionStatus::Complete;
}
return transaction;
}
TransactionInfo parseExpandedInfo(const QDomElement &root) {
if (root.isElement()) {
QDomElement elem = root.toElement();
if (elem.tagName() == "node"
&& elem.attribute("class") == "android.widget.Button"
&& elem.attribute("text") == "Детали операции"
) {
TransactionInfo transaction;
// Все что ниже деталей операции
QDomNodeList detailInfo = elem.parentNode().childNodes();
for (int i = 0; i < detailInfo.count(); ++i) {
QDomNode info = detailInfo.at(i);
if (!info.isElement()) continue;
QDomElement infoElement = info.toElement();
QString index = infoElement.attribute("index");
QDomElement ee;
switch (index.toInt()) {
case 3:
transaction.bankTime = transformBankDateTime(findSubInfo(infoElement, "1"));
break;
case 4:
ee = findSubElement(infoElement, "0");
ee = findSubElement(ee, "1");
transaction.phone = findSubInfo(ee, "0").remove(onlyDigitsRegex);
break;
case 5:
transaction.name = findSubInfo(infoElement, "1");
break;
case 6:
transaction.bankName = findSubInfo(infoElement, "1");
break;
case 9:
transaction.fee = findSubInfo(infoElement, "1").remove(onlyDigitsAndDotRegex).toFloat();
break;
case 10:
ee = findSubElement(infoElement, "1");
transaction.bankTrExternalId = findSubInfo(ee, "1");
break;
default:
break;
}
}
return transaction;
}
}
// Рекурсивно обходим всех детей
QDomNode child = root.firstChild();
while (!child.isNull()) {
if (TransactionInfo info = parseExpandedInfo(child.toElement()); !info.bankTime.isEmpty()) {
return info;
}
child = child.nextSibling();
}
return {};
}
TransactionInfo ScreenXmlZiraatParser::parseTransactionInfo(int screenWidth, int screenHeight) {
// 1. Распарсить описание транзакции
TransactionInfo transaction = parseExpandedInfo(m_xml);
// 1. Найти статус: Исполнен | Операция исполнена
transaction.status = TransactionStatus::Wait;
Node status1 = findTextNode("Исполнен", 0, 0, screenWidth, screenHeight / 2);
Node status2 = findTextNode("Операция исполнена", 0, 0, screenWidth, screenHeight / 2);
if (!status1.isEmpty() || !status2.isEmpty()) {
transaction.status = TransactionStatus::Complete;
}
return transaction;
}
QList<AccountInfo> ScreenXmlZiraatParser::parseAccountsInfo() {
const QDomNodeList nodes = m_xml.elementsByTagName("node");
QList<AccountInfo> accounts;
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();
}
}
}
return accounts;
}
void ScreenXmlZiraatParser::test() {
// TransactionInfo info2 = parseExpandedInfo(m_xml);
// qDebug().noquote().nospace() << convertTransactionToString(info2);
// return;
QFile file("assets/rshb/all_products.xml");
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug() << "Cannot open file";
}
QDomDocument doc;
if (!doc.setContent(&file)) {
qDebug() << "Failed to parse XML";
}
file.close();
qDebug() << "Start parsing XML:";
// Номер карты с последними цифрами
QDomElement root = doc.documentElement();
parseAndSaveXml(doc.toString());
// for (const AccountInfo &info: parseAccountsInfo()) {
// qDebug().noquote().nospace() << convertAccountToString(info);
// }
// TransactionInfo info = parseExpandedInfo(root);
// qDebug().noquote().nospace() << convertTransactionToString(info);
// ЕСПП
}