733 lines
28 KiB
C++
733 lines
28 KiB
C++
#include "ScreenXmlBirbankParser.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"
|
||
|
||
ScreenXmlBirbankParser::ScreenXmlBirbankParser(QObject *parent) : QObject(parent) {
|
||
}
|
||
|
||
ScreenXmlBirbankParser::~ScreenXmlBirbankParser() = default;
|
||
|
||
static const QRegularExpression boundsRegex(R"(\[([0-9]+),([0-9]+)\]\[([0-9]+),([0-9]+)\])");
|
||
static const QRegularExpression onlyDigitsRegex(R"([^0-9])");
|
||
static const QRegularExpression onlyDigitsAndDotRegex(R"([^0-9.])");
|
||
|
||
static const QRegularExpression rePhone(R"raw((\+7\(?\d{3}\)?[- ]?\d{3}[- ]?\d{2}[- ]?\d{2}))raw");
|
||
static const QRegularExpression reFromBank(R"raw(из (.+?) на)raw");
|
||
static const QRegularExpression reToBank(R"raw(в (.+?) через)raw");
|
||
static const QRegularExpression reSbpId(R"(СБП(?: С2С)? ([A-Z0-9]{30,}))");
|
||
static const QRegularExpression reIdAlt(R"(ID перевода (\d+))");
|
||
static const QRegularExpression reDate(R"((?:Дата операции|дата) (\d{2}\.\d{2}\.\d{4}(?: \d{2}:\d{2}:\d{2})?))");
|
||
static const QRegularExpression reSender(R"(от ([^\(]+))");
|
||
static const QRegularExpression reNameAlt(R"(Перевод ([А-Яа-яЁё\s\*]+) в)");
|
||
|
||
static const QRegularExpression reCardData(R"(цифрами\s+(\d\s\d\s\d\s\d).*?Сумма:\s([\d\s]+)\sруб)");
|
||
|
||
Node ScreenXmlBirbankParser::tryToFindBannerOrDialog(const int width, const int height) {
|
||
// 1. Ищем push banner - он перекрывает весь экран
|
||
Node first = m_nodes[0];
|
||
for (const Node &node: m_nodes) {
|
||
// Если это однозначно, что пуш-баннер
|
||
if (node.resourceId == "aft-popup-pushNotification-iconClose" && node.className == "android.widget.Button") {
|
||
return node;
|
||
}
|
||
// Если есть кнопка "Позже"
|
||
if (node.text.trimmed() == "Позже" && node.className == "android.widget.Button") {
|
||
return node;
|
||
}
|
||
// Если нашли пустую кнпоку
|
||
if (node.className == "android.widget.Button" && node.clickable
|
||
&& node.text.isEmpty() && node.contentDesc.isEmpty()) {
|
||
// <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 ScreenXmlBirbankParser::findAccountButton(const QString &numbers) {
|
||
// TODO возможно это не так, надо уточнять
|
||
return findTextNode(QString("LEVENT - %1").arg(numbers));
|
||
}
|
||
|
||
/**
|
||
* Смотрим что есть текс "Мы заботимся о вашей безопасности" и
|
||
* кнопка "Отложить"
|
||
* @return координаты кнопки для закрытия
|
||
*/
|
||
Node ScreenXmlBirbankParser::tryToFindSecurityBanner() {
|
||
Node text;
|
||
Node closeBtn;
|
||
for (const Node &node: m_nodes) {
|
||
if (node.text.trimmed() == "Мы заботимся о вашей безопасности" && node.className == "android.app.Dialog") {
|
||
text = node;
|
||
}
|
||
if (node.text.trimmed() == "Отложить" && node.className == "android.widget.Button") {
|
||
closeBtn = node;
|
||
}
|
||
}
|
||
|
||
// Не нужно проверять все, если хотя бы одна координата есть
|
||
if (!text.isEmpty() && !closeBtn.isEmpty()) {
|
||
return closeBtn;
|
||
}
|
||
return {};
|
||
}
|
||
|
||
Node ScreenXmlBirbankParser::tryToFindSelectBottomSheet() {
|
||
Node closeBtn;
|
||
for (const Node &node: m_nodes) {
|
||
if ((node.text.trimmed() == "Отмена" || node.text.trimmed() == "Отложить") && node.className ==
|
||
"android.widget.Button") {
|
||
closeBtn = node;
|
||
}
|
||
}
|
||
// Не нужно проверять все, если хотя бы одна координата есть
|
||
if (!closeBtn.isEmpty()) {
|
||
return closeBtn;
|
||
}
|
||
return {};
|
||
}
|
||
|
||
Node ScreenXmlBirbankParser::findTextNode(const QString &text) {
|
||
for (const Node &node: m_nodes) {
|
||
if (node.className == "android.widget.TextView" && node.text.trimmed() == text) {
|
||
return node;
|
||
}
|
||
}
|
||
return {};
|
||
}
|
||
|
||
Node ScreenXmlBirbankParser::findTextNode(const QString &text, const int x1, const int y1, const int x2, const int y2) {
|
||
for (const Node &node: m_nodes) {
|
||
if (node.className == "android.widget.TextView" && node.text.trimmed() == text
|
||
&& node.x() >= x1 && node.x() <= x2 && node.y() >= y1 && node.y() <= y2) {
|
||
return node;
|
||
}
|
||
}
|
||
return {};
|
||
}
|
||
|
||
Node ScreenXmlBirbankParser::findEditText(const QString &text) {
|
||
for (const Node &node: m_nodes) {
|
||
if (node.className == "android.widget.EditText" && node.text == text) {
|
||
return node;
|
||
}
|
||
}
|
||
return {};
|
||
}
|
||
|
||
Node ScreenXmlBirbankParser::findEditTextByResourceId(const QString &resourceId) {
|
||
for (const Node &node: m_nodes) {
|
||
if (node.className == "android.widget.EditText" && node.resourceId == resourceId) {
|
||
return node;
|
||
}
|
||
}
|
||
return {};
|
||
}
|
||
|
||
Node ScreenXmlBirbankParser::findButtonNode(const QString &text, const bool contains) {
|
||
for (const Node &node: m_nodes) {
|
||
if (contains) {
|
||
if (node.className == "android.widget.Button" && node.text.contains(text)) {
|
||
return node;
|
||
}
|
||
} else {
|
||
if (node.className == "android.widget.Button" && node.text.trimmed() == text) {
|
||
return node;
|
||
}
|
||
}
|
||
}
|
||
return {};
|
||
}
|
||
|
||
bool ScreenXmlBirbankParser::isHomeScreen() {
|
||
Node activitiesBtn;
|
||
for (const Node &node: m_nodes) {
|
||
if (node.text.trimmed() == "Карты и счета") {
|
||
activitiesBtn = node;
|
||
}
|
||
}
|
||
return !activitiesBtn.isEmpty();
|
||
}
|
||
|
||
QList<Node> ScreenXmlBirbankParser::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.TextView" && 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> ScreenXmlBirbankParser::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 ScreenXmlBirbankParser::parseAndSaveXml(const QString &xml) {
|
||
QDomDocument doc;
|
||
doc.setContent(xml);
|
||
m_xml = doc.documentElement();
|
||
m_nodes.clear();
|
||
|
||
QXmlStreamReader reader(xml);
|
||
while (!reader.atEnd()) {
|
||
reader.readNext();
|
||
if (reader.isStartElement() && reader.name() == "node") {
|
||
Node node = Node();
|
||
if (reader.attributes().hasAttribute("bounds")) {
|
||
QString bounds = reader.attributes().value("bounds").toString();
|
||
if (QRegularExpressionMatch match = boundsRegex.match(bounds); match.hasMatch()) {
|
||
node.setBounds(
|
||
match.captured(1).toInt(),
|
||
match.captured(2).toInt(),
|
||
match.captured(3).toInt(),
|
||
match.captured(4).toInt()
|
||
);
|
||
}
|
||
}
|
||
if (reader.attributes().hasAttribute("text")) {
|
||
node.text = reader.attributes().value("text").toString();
|
||
}
|
||
|
||
if (reader.attributes().hasAttribute("content-desc")) {
|
||
node.contentDesc = reader.attributes().value("content-desc").toString();
|
||
}
|
||
|
||
if (reader.attributes().hasAttribute("class")) {
|
||
node.className = reader.attributes().value("class").toString();
|
||
}
|
||
|
||
if (reader.attributes().hasAttribute("clickable")) {
|
||
node.clickable = reader.attributes().value("clickable").toString() == "true";
|
||
}
|
||
if (reader.attributes().hasAttribute("focusable")) {
|
||
node.focusable = reader.attributes().value("focusable").toString() == "true";
|
||
}
|
||
if (reader.attributes().hasAttribute("resource-id")) {
|
||
node.resourceId = reader.attributes().value("resource-id").toString();
|
||
}
|
||
|
||
m_nodes.append(node);
|
||
}
|
||
}
|
||
}
|
||
|
||
QDomElement findSubElementBirbank(const QDomElement &elem, const QString &index) {
|
||
if (elem.hasChildNodes()) {
|
||
QDomNodeList subInfoList = elem.childNodes();
|
||
for (QDomNode subInfoNode: subInfoList) {
|
||
if (!subInfoNode.isElement()) continue;
|
||
QDomElement subInfo = subInfoNode.toElement();
|
||
if (subInfo.attribute("index") == index) {
|
||
return subInfo;
|
||
}
|
||
}
|
||
}
|
||
return {};
|
||
}
|
||
|
||
QString findSubInfoBirbank(const QDomElement &elem, const QString &index) {
|
||
QDomElement subInfo = findSubElementBirbank(elem, index);
|
||
if (!subInfo.isNull()) {
|
||
return subInfo.attribute("text");
|
||
}
|
||
return "";
|
||
}
|
||
|
||
bool hasTimeComponentBirbank(const QString &datetimeStr) {
|
||
QDateTime dt = QDateTime::fromString(datetimeStr, "dd.MM.yyyy HH:mm:ss");
|
||
if (!dt.isValid()) {
|
||
return false; // Строка не соответствует формату даты и времени
|
||
}
|
||
return dt.time() != QTime(0, 0, 0); // Проверка, что время не равно 00:00:00
|
||
}
|
||
|
||
QString transformBankDateTimeBirbank(const QString &date) {
|
||
QDateTime dt;
|
||
if (date.contains(",")) {
|
||
// 10.05.2025, 15:03
|
||
dt = QDateTime::fromString(date, "dd.MM.yyyy, HH:mm");
|
||
} else {
|
||
dt = QDateTime::fromString(date, "dd.MM.yyyy HH:mm:ss");
|
||
}
|
||
if (!dt.isValid()) {
|
||
dt = QDateTime::fromString(date, "dd.MM.yyyy HH:mm");
|
||
}
|
||
if (!dt.isValid()) {
|
||
return date; // не распарсили
|
||
}
|
||
return dt.toString("dd.MM.yyyy HH:mm:ss");
|
||
}
|
||
|
||
void parseTransferInfoBirbank(const QString &text, TransactionInfo &info) {
|
||
if (const auto m = reSender.match(text); m.hasMatch()) {
|
||
info.name = m.captured(1).trimmed();
|
||
} else if (const auto m1 = reNameAlt.match(text); m1.hasMatch()) {
|
||
info.name = m1.captured(1).trimmed();
|
||
}
|
||
|
||
if (const auto m = rePhone.match(text); m.hasMatch()) {
|
||
info.phone = m.captured(1).trimmed().remove(onlyDigitsRegex);
|
||
}
|
||
|
||
if (const auto m = reFromBank.match(text); m.hasMatch()) {
|
||
if (const QString bank = m.captured(1).trimmed(); !bank.isEmpty()) {
|
||
info.bankName = bank;
|
||
}
|
||
} else if (const auto m1 = reToBank.match(text); m1.hasMatch()) {
|
||
if (const QString bank = m1.captured(1).trimmed(); !bank.isEmpty()) {
|
||
info.bankName = bank;
|
||
}
|
||
}
|
||
|
||
if (const auto m = reSbpId.match(text); m.hasMatch()) {
|
||
info.bankTrExternalId = m.captured(1);
|
||
} else if (const auto m1 = reIdAlt.match(text); m1.hasMatch()) {
|
||
info.bankTrExternalId = m1.captured(1);
|
||
}
|
||
|
||
if (const auto m = reDate.match(text); m.hasMatch()) {
|
||
const QString date = m.captured(1);
|
||
// если не пустая и дата у нас с 00:00
|
||
// if (hasTimeComponent2(date)) {
|
||
info.bankTime = transformBankDateTimeBirbank(date);
|
||
// }
|
||
}
|
||
}
|
||
|
||
void traverseNodesBirbank(const QDomNode &node) {
|
||
if (node.isElement()) {
|
||
QDomElement elem = node.toElement();
|
||
|
||
if (node.isElement()) {
|
||
QDomElement elem = node.toElement();
|
||
|
||
if (elem.tagName() == "node" &&
|
||
elem.attribute("class") == "android.widget.Button" &&
|
||
elem.attribute("text") == "Детали операции") {
|
||
// Все что ниже деталей операции
|
||
QDomNodeList detailInfo = elem.parentNode().childNodes();
|
||
|
||
|
||
for (int j = 0; j < detailInfo.count(); ++j) {
|
||
QDomNode info = detailInfo.at(j);
|
||
if (!info.isElement()) continue;
|
||
QDomElement infoElement = info.toElement();
|
||
QString index = infoElement.attribute("index");
|
||
switch (index.toInt()) {
|
||
case 2:
|
||
qDebug() << "Дата операции:" << findSubInfoBirbank(infoElement, "1");
|
||
break;
|
||
case 5:
|
||
qDebug() << "Название:" << findSubInfoBirbank(infoElement, "1");
|
||
qDebug() << "-------------------------------------";
|
||
// parseTransfer(findSubInfo2(infoElement));
|
||
break;
|
||
case 7:
|
||
qDebug() << "Комиссия:" << findSubInfoBirbank(infoElement, "1");
|
||
break;
|
||
default:
|
||
qDebug() << "Прочее:" << findSubInfoBirbank(infoElement, "1");
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Рекурсивно обходим всех детей
|
||
QDomNode child = node.firstChild();
|
||
while (!child.isNull()) {
|
||
traverseNodesBirbank(child);
|
||
child = child.nextSibling();
|
||
}
|
||
}
|
||
|
||
QDomNode findNodeBirbank(const QDomNode &node, const QString &text) {
|
||
if (node.isElement()) {
|
||
QString nodeText = node.toElement().attribute("text");
|
||
if (nodeText.toLower().startsWith(text)) {
|
||
return node;
|
||
}
|
||
}
|
||
|
||
|
||
QDomNode child = node.firstChild();
|
||
while (!child.isNull()) {
|
||
QDomNode result = findNodeBirbank(child, text);
|
||
if (!result.isNull()) {
|
||
return result;
|
||
}
|
||
child = child.nextSibling();
|
||
}
|
||
return {};
|
||
}
|
||
|
||
float extractAmountsBirbank(const QString &text) {
|
||
// Разрешаем пробелы внутри чисел, захватываем "рубль", "рубля", "рублей" и т.д.
|
||
// QRegularExpression re(R"((Плюс|Минус)\s+([\d\s]+)\s+рубл\w*\s+(\d+)\s+копе\w*)");
|
||
QRegularExpression re(
|
||
R"((Плюс|Минус)\s+([\d\s]+)\s+рубл\w*\s+(\d+)\s+копе\w*)",
|
||
QRegularExpression::UseUnicodePropertiesOption
|
||
);
|
||
|
||
QRegularExpressionMatch match = re.match(text);
|
||
if (match.hasMatch()) {
|
||
QString signStr = match.captured(1); // "Плюс" или "Минус"
|
||
QString rublesStr = match.captured(2).remove(' '); // "1 000" → "1000"
|
||
int rubles = rublesStr.toInt();
|
||
int kopecks = match.captured(3).toInt();
|
||
|
||
int totalKopecks = rubles * 100 + kopecks;
|
||
if (signStr == "Минус") {
|
||
totalKopecks *= -1;
|
||
}
|
||
|
||
return totalKopecks / 100.0f;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
QString beforeAmountBirbank(const QString &text) {
|
||
int index = text.indexOf(QRegularExpression("Плюс|Минус"));
|
||
if (index != -1) {
|
||
return text.left(index).trimmed();
|
||
}
|
||
return text; // если не найдено — вернуть всю строку
|
||
}
|
||
|
||
double parseAmountBirbank(const QString &amountAsString) {
|
||
QString numberString = amountAsString;
|
||
numberString.remove(QRegularExpression(R"([^\dточка])"));
|
||
numberString.replace("точка", ".");
|
||
double amount = numberString.toDouble();
|
||
|
||
if (amountAsString.startsWith("-")) {
|
||
amount *= -1;
|
||
}
|
||
|
||
return amount;
|
||
}
|
||
|
||
QDomElement findElementWithPaymentsBirbank(const QDomElement &parent, const QString &resourceId) {
|
||
QDomElement element = parent.firstChildElement();
|
||
while (!element.isNull()) {
|
||
QDomElement child = element.firstChildElement();
|
||
while (!child.isNull()) {
|
||
if (child.hasAttribute("resource-id") && child.attribute("resource-id") == resourceId) {
|
||
return child;
|
||
}
|
||
child = child.nextSiblingElement();
|
||
}
|
||
|
||
QDomElement found = findElementWithPaymentsBirbank(element, resourceId);
|
||
if (!found.isNull()) {
|
||
return found;
|
||
}
|
||
element = element.nextSiblingElement();
|
||
}
|
||
return {};
|
||
}
|
||
|
||
QDateTime getUtcFromBakuDate(const QString &date, const QString &time) {
|
||
int year = QDate::currentDate().year(); // Или укажи вручную нужный год
|
||
QString combined = QString("%1 %2 %3").arg(date).arg(year).arg(time); // "24 июля 2025 0:31"
|
||
|
||
QLocale ruLocale(QLocale::Russian, QLocale::Russia);
|
||
QDateTime dt = ruLocale.toDateTime(combined, "d MMMM yyyy H:mm");
|
||
|
||
if (dt.isValid()) {
|
||
// Установка часового пояса Asia/Baku
|
||
dt.setTimeZone(QTimeZone("Asia/Baku"));
|
||
// Перевод в UTC
|
||
QDateTime utc = dt.toTimeZone(QTimeZone::utc());
|
||
return utc;
|
||
} else {
|
||
qWarning() << "Ошибка разбора даты/времени";
|
||
}
|
||
return {};
|
||
}
|
||
|
||
|
||
QList<TransactionInfo> ScreenXmlBirbankParser::parsePositiveTransactions() {
|
||
QList<TransactionInfo> transactions;
|
||
|
||
QDomElement child = findElementWithPaymentsBirbank(m_xml, "az.kapitalbank.mbanking:id/rv_pfm_operations");
|
||
// QDomElement child = findElementWithPaymentsBirbank(m_xml, "az.kapitalbank.mbanking:id/rv_operations");
|
||
QDomNodeList listView = child.childNodes();
|
||
qDebug() << listView.count();
|
||
if (!listView.isEmpty()) {
|
||
QString date = "";
|
||
|
||
for (QDomNode node: listView) {
|
||
const QDomNodeList trBoxGroup = node.childNodes();
|
||
if (trBoxGroup.count() > 0) {
|
||
TransactionInfo transaction;
|
||
for (QDomNode item: trBoxGroup) {
|
||
QDomElement groupItem = item.toElement();
|
||
if ("az.kapitalbank.mbanking:id/tv_date" == groupItem.attribute("resource-id")) {
|
||
date = groupItem.attribute("text");
|
||
} else if ("az.kapitalbank.mbanking:id/transaction_title" == groupItem.attribute("resource-id")) {
|
||
transaction.description = groupItem.attribute("text");
|
||
} else if ("az.kapitalbank.mbanking:id/transaction_time" == groupItem.attribute("resource-id")) {
|
||
transaction.bankTime = date + " " + groupItem.attribute("text");
|
||
transaction.completeTime = getUtcFromBakuDate(date, groupItem.attribute("text"));
|
||
} else if ("az.kapitalbank.mbanking:id/transaction_amount" == groupItem.attribute("resource-id")) {
|
||
auto amount = groupItem.attribute("text");
|
||
if (amount.startsWith("+")) {
|
||
transaction.amount = amount.remove("+").remove("₼").replace(",", ".").trimmed().toFloat();
|
||
}
|
||
}
|
||
}
|
||
if (transaction.amount > 0) {
|
||
transactions.append(transaction);
|
||
qDebug().noquote().nospace() << convertTransactionToString(transaction);
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
qDebug() << "ListView is empty";
|
||
}
|
||
return transactions;
|
||
}
|
||
|
||
|
||
TransactionInfo parseExpandedInfoHistoryBirbank(const QDomElement &root) {
|
||
if (root.isElement()) {
|
||
QDomElement elem = root.toElement();
|
||
if (elem.tagName() == "node"
|
||
&& elem.attribute("class") == "android.widget.Button"
|
||
&& elem.attribute("text") == "Детали операции"
|
||
) {
|
||
TransactionInfo transaction;
|
||
// Все что ниже деталей операции
|
||
QDomNodeList detailInfo = elem.parentNode().childNodes();
|
||
|
||
for (int i = 0; i < detailInfo.count(); ++i) {
|
||
QDomNode info = detailInfo.at(i);
|
||
if (!info.isElement()) continue;
|
||
|
||
switch (QDomElement e = info.toElement(); e.attribute("index").toInt()) {
|
||
case 2:
|
||
// Дата операции
|
||
transaction.bankTime = transformBankDateTimeBirbank(findSubInfoBirbank(e, "1"));
|
||
break;
|
||
case 5:
|
||
transaction.description = findSubInfoBirbank(e, "1");
|
||
parseTransferInfoBirbank(transaction.description, transaction);
|
||
break;
|
||
case 7:
|
||
// Комиссия
|
||
transaction.fee = findSubInfoBirbank(e, "1").remove(onlyDigitsRegex).toFloat();
|
||
break;
|
||
default:
|
||
// qDebug() << "Прочее:" << findSubInfo2(e);
|
||
break;
|
||
}
|
||
}
|
||
return transaction;
|
||
}
|
||
}
|
||
|
||
// Рекурсивно обходим всех детей
|
||
QDomNode child = root.firstChild();
|
||
while (!child.isNull()) {
|
||
if (TransactionInfo info = parseExpandedInfoHistoryBirbank(child.toElement()); !info.bankTime.isEmpty()) {
|
||
return info;
|
||
}
|
||
child = child.nextSibling();
|
||
}
|
||
|
||
return {};
|
||
}
|
||
|
||
TransactionInfo ScreenXmlBirbankParser::parseTransactionInfoHistory(int screenWidth, int screenHeight) {
|
||
// 1. Распарсить описание транзакции
|
||
TransactionInfo transaction = parseExpandedInfoHistoryBirbank(m_xml);
|
||
// 1. Найти статус: Исполнен | Операция исполнена
|
||
transaction.status = TransactionStatus::Wait;
|
||
Node status1 = findTextNode("Исполнен", 0, 0, screenWidth, screenHeight / 2);
|
||
Node status2 = findTextNode("Операция исполнена", 0, 0, screenWidth, screenHeight / 2);
|
||
if (!status1.isEmpty() || !status2.isEmpty()) {
|
||
transaction.status = TransactionStatus::Complete;
|
||
}
|
||
return transaction;
|
||
}
|
||
|
||
TransactionInfo parseExpandedInfoBirbank(const QDomElement &root) {
|
||
if (root.isElement()) {
|
||
QDomElement elem = root.toElement();
|
||
if (elem.tagName() == "node"
|
||
&& elem.attribute("class") == "android.widget.Button"
|
||
&& elem.attribute("text") == "Детали операции"
|
||
) {
|
||
TransactionInfo transaction;
|
||
// Все что ниже деталей операции
|
||
QDomNodeList detailInfo = elem.parentNode().childNodes();
|
||
|
||
for (int i = 0; i < detailInfo.count(); ++i) {
|
||
QDomNode info = detailInfo.at(i);
|
||
if (!info.isElement()) continue;
|
||
|
||
QDomElement infoElement = info.toElement();
|
||
QString index = infoElement.attribute("index");
|
||
QDomElement ee;
|
||
switch (index.toInt()) {
|
||
case 3:
|
||
transaction.bankTime = transformBankDateTimeBirbank(findSubInfoBirbank(infoElement, "1"));
|
||
break;
|
||
case 4:
|
||
ee = findSubElementBirbank(infoElement, "0");
|
||
ee = findSubElementBirbank(ee, "1");
|
||
transaction.phone = findSubInfoBirbank(ee, "0").remove(onlyDigitsRegex);
|
||
break;
|
||
case 5:
|
||
transaction.name = findSubInfoBirbank(infoElement, "1");
|
||
break;
|
||
case 6:
|
||
transaction.bankName = findSubInfoBirbank(infoElement, "1");
|
||
break;
|
||
case 9:
|
||
transaction.fee = findSubInfoBirbank(infoElement, "1").remove(onlyDigitsAndDotRegex).toFloat();
|
||
break;
|
||
case 10:
|
||
ee = findSubElementBirbank(infoElement, "1");
|
||
transaction.bankTrExternalId = findSubInfoBirbank(ee, "1");
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
return transaction;
|
||
}
|
||
}
|
||
|
||
// Рекурсивно обходим всех детей
|
||
QDomNode child = root.firstChild();
|
||
while (!child.isNull()) {
|
||
if (TransactionInfo info = parseExpandedInfoBirbank(child.toElement()); !info.bankTime.isEmpty()) {
|
||
return info;
|
||
}
|
||
child = child.nextSibling();
|
||
}
|
||
|
||
return {};
|
||
}
|
||
|
||
TransactionInfo ScreenXmlBirbankParser::parseTransactionInfo(int screenWidth, int screenHeight) {
|
||
// 1. Распарсить описание транзакции
|
||
TransactionInfo transaction = parseExpandedInfoBirbank(m_xml);
|
||
// 1. Найти статус: Исполнен | Операция исполнена
|
||
transaction.status = TransactionStatus::Wait;
|
||
Node status1 = findTextNode("Исполнен", 0, 0, screenWidth, screenHeight / 2);
|
||
Node status2 = findTextNode("Операция исполнена", 0, 0, screenWidth, screenHeight / 2);
|
||
if (!status1.isEmpty() || !status2.isEmpty()) {
|
||
transaction.status = TransactionStatus::Complete;
|
||
}
|
||
return transaction;
|
||
}
|
||
|
||
|
||
QList<AccountInfo> ScreenXmlBirbankParser::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("text").contains("₼")) {
|
||
// Нашли сумму
|
||
// el.attribute("text") - ₼ и трим
|
||
auto amount = el.attribute("text").replace("₼", "").trimmed();
|
||
const QDomElement parent = el.parentNode().parentNode().toElement();
|
||
// Перебираем потомков
|
||
QDomNode n = parent.firstChild().firstChild().firstChild().firstChild();
|
||
while (!n.isNull()) {
|
||
const QDomElement e = n.toElement();
|
||
if (e.attribute("resource-id") == "az.kapitalbank.mbanking:id/tvNumber") {
|
||
const QString account = e.attribute("text");
|
||
|
||
AccountInfo info;
|
||
info.lastNumbers = account;
|
||
info.amount = amount.toFloat();
|
||
info.description = account;
|
||
info.updateTime = DateUtils::getUtcNow();
|
||
|
||
accounts.append(info);
|
||
break;
|
||
}
|
||
n = n.nextSibling();
|
||
}
|
||
}
|
||
}
|
||
return accounts;
|
||
}
|
||
|
||
void ScreenXmlBirbankParser::test() {
|
||
// TransactionInfo info2 = parseExpandedInfo(m_xml);
|
||
// qDebug().noquote().nospace() << convertTransactionToString(info2);
|
||
// return;
|
||
|
||
QFile file("assets/rshb/all_products.xml");
|
||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||
qDebug() << "Cannot open file";
|
||
}
|
||
|
||
QDomDocument doc;
|
||
if (!doc.setContent(&file)) {
|
||
qDebug() << "Failed to parse XML";
|
||
}
|
||
file.close();
|
||
|
||
qDebug() << "Start parsing XML:";
|
||
|
||
// Номер карты с последними цифрами
|
||
QDomElement root = doc.documentElement();
|
||
parseAndSaveXml(doc.toString());
|
||
// for (const AccountInfo &info: parseAccountsInfo()) {
|
||
// qDebug().noquote().nospace() << convertAccountToString(info);
|
||
// }
|
||
// TransactionInfo info = parseExpandedInfo(root);
|
||
// qDebug().noquote().nospace() << convertTransactionToString(info);
|
||
// ЕСПП
|
||
}
|