Add enhanced account and transaction handling features
Refactored code to improve account and transaction data parsing and management, introducing support for additional fields like card numbers and account statuses. Added new utilities for database connections, device-specific screenshot handling, and XML parsing for enriched account and transaction details.
This commit is contained in:
parent
15a9e788eb
commit
59cef50ad3
@ -104,6 +104,7 @@ QString AdbUtils::getScreenDumpAsXml(const QString &deviceId, const int idx) {
|
||||
process.start("sh", {"-c", xml});
|
||||
process.waitForFinished();
|
||||
|
||||
if (false) {
|
||||
// TODO делаем скрин, убрать потом
|
||||
// adb -s 6edc4a47 shell 'screencap -p /sdcard/screen.png' && adb -s 6edc4a47 pull /sdcard/screen.png screen_step_1.png && adb -s 6edc4a47 shell 'rm /sdcard/screen.png'
|
||||
QProcess processScreenshot;
|
||||
@ -114,6 +115,7 @@ QString AdbUtils::getScreenDumpAsXml(const QString &deviceId, const int idx) {
|
||||
).arg(deviceId, QString::number(idx));
|
||||
processScreenshot.start("sh", {"-c", screenshot});
|
||||
processScreenshot.waitForFinished();
|
||||
}
|
||||
|
||||
// чтобы убедиться, что мы прочитали без ошибок
|
||||
if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) {
|
||||
@ -128,6 +130,18 @@ QString AdbUtils::getScreenDumpAsXml(const QString &deviceId, const int idx) {
|
||||
return QString::fromUtf8(xmlBytes);
|
||||
}
|
||||
|
||||
bool AdbUtils::takeScreenshot(const QString &deviceId, const QString &name) {
|
||||
QProcess process;
|
||||
QString screenshot = QString(
|
||||
"adb -s %1 shell 'screencap -p /sdcard/screen.png' && "
|
||||
"adb -s %1 pull /sdcard/screen.png screen_%2.png && "
|
||||
"adb -s %1 shell 'rm /sdcard/screen.png'"
|
||||
).arg(deviceId, name);
|
||||
process.start("sh", {"-c", screenshot});
|
||||
process.waitForFinished();
|
||||
return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
|
||||
}
|
||||
|
||||
bool AdbUtils::makeTap(const QString &deviceId, const int x, const int y) {
|
||||
QProcess process;
|
||||
process.start("adb", {
|
||||
|
||||
@ -17,7 +17,9 @@ public:
|
||||
|
||||
static bool tryToKillApplication(const QString &deviceId, const QString &packageName);
|
||||
|
||||
static QString getScreenDumpAsXml(const QString &deviceId, const int idx);
|
||||
static QString getScreenDumpAsXml(const QString &deviceId, int idx);
|
||||
|
||||
static bool takeScreenshot(const QString &deviceId, const QString &name);
|
||||
|
||||
static bool makeTap(const QString &deviceId, int x, int y);
|
||||
|
||||
|
||||
230
android/rshb/CommonScript.cpp
Normal file
230
android/rshb/CommonScript.cpp
Normal file
@ -0,0 +1,230 @@
|
||||
#include "CommonScript.h"
|
||||
#include <QRandomGenerator>
|
||||
#include <QThread>
|
||||
|
||||
#include "adb/AdbUtils.h"
|
||||
|
||||
CommonScript::CommonScript(QObject *parent)
|
||||
: QObject(parent) {
|
||||
}
|
||||
|
||||
CommonScript::~CommonScript() = default;
|
||||
|
||||
void CommonScript::start() {
|
||||
doStart();
|
||||
emit finished();
|
||||
}
|
||||
|
||||
Node CommonScript::swipeToButton(
|
||||
const QString &deviceId,
|
||||
const QString &text, int &counter, const int width, const int height
|
||||
) {
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
if (Node button = xmlScreenParser.findButtonNode(text, true); button.x() > 0 && button.y() > 0) {
|
||||
return button;
|
||||
}
|
||||
const int x = width / 2;
|
||||
const int offset = height / 4;
|
||||
AdbUtils::makeSwipe(deviceId, x, height - offset, x, offset);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool CommonScript::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) {
|
||||
return false;
|
||||
}
|
||||
QThread::msleep(QRandomGenerator::global()->bounded(100, 901));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CommonScript::goToHomeScreen(
|
||||
const QString &deviceId,
|
||||
const QString &packageName,
|
||||
const QString &pinCode,
|
||||
const int width,
|
||||
const int height
|
||||
) {
|
||||
// Запустить устройство
|
||||
if (const QString pid = AdbUtils::tryToGetRunningAppPid(deviceId, packageName); !pid.isEmpty()) {
|
||||
qDebug() << "Process already running, pid: " << pid;
|
||||
AdbUtils::getScreenDumpAsXml(deviceId, 1000);
|
||||
AdbUtils::tryToKillApplication(deviceId, packageName);
|
||||
}
|
||||
if (!AdbUtils::tryToStartApplication(deviceId, packageName)) {
|
||||
AdbUtils::takeScreenshot(deviceId, "run_app_error");
|
||||
qWarning() << "Process has not started";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Даем минимальное время на запуск
|
||||
QThread::msleep(4500);
|
||||
int counter = 0;
|
||||
int restarted = 0;
|
||||
int totalCounter = 0;
|
||||
bool findAndInputPincode = false;
|
||||
while (true) {
|
||||
counter++;
|
||||
totalCounter++;
|
||||
|
||||
// 3 раза уже перезапускали приложение, завершаем попытки
|
||||
if (restarted > 2) {
|
||||
AdbUtils::takeScreenshot(deviceId, "3_times_try_start");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Если более 30 секунд мы пытаемся перейти на домашнюю, перезапускаем
|
||||
if (counter > 30) {
|
||||
AdbUtils::tryToKillApplication(deviceId, packageName);
|
||||
QThread::msleep(4500);
|
||||
++restarted;
|
||||
counter = 0;
|
||||
}
|
||||
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, totalCounter + 1));
|
||||
|
||||
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();
|
||||
if (!closeBannerNode.isEmpty()) {
|
||||
AdbUtils::makeTap(deviceId, closeBannerNode.x(), closeBannerNode.y());
|
||||
QThread::msleep(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Проверяем, что это экран ввода пин-кода
|
||||
if (!findAndInputPincode) {
|
||||
if (const QList<Node> pincode = xmlScreenParser.tryToFindPinCode(pinCode, "Введите ПИН");
|
||||
pincode.size() == 4) {
|
||||
inputPinCode(deviceId, pincode);
|
||||
// После ввода пин-код нужно сделать задержку, пока подгрузка идет, чтобы снова не спарсить
|
||||
findAndInputPincode = true;
|
||||
QThread::msleep(3000);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
QThread::msleep(1000);
|
||||
|
||||
// Если что-то пойдет не по плану...
|
||||
if (totalCounter > 100) {
|
||||
AdbUtils::takeScreenshot(deviceId, "too_many_attempts");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CommonScript::runAppAndGoToHomeScreen() {
|
||||
const QString deviceId = "6edc4a47";
|
||||
const QString packageName = "ru.rshb.dbo";
|
||||
const QString pinCode = "1102";
|
||||
const int width = 720;
|
||||
const int height = 1600;
|
||||
|
||||
const QString cardNumber = "7 1 8 3";
|
||||
// В том числе и данные на каком устройстве это делать
|
||||
// TODO тут код работы с БД
|
||||
|
||||
// +7 (964) 181-51-46
|
||||
const QString phoneNumber = "+79641815146";
|
||||
const QString bankName = "Альфа-Банк";
|
||||
const QString paySum = "250";
|
||||
|
||||
xmlScreenParser.test();
|
||||
return false;
|
||||
// выше достаем данные и все прочее
|
||||
|
||||
return goToHomeScreen(deviceId, packageName, pinCode, width, height);
|
||||
}
|
||||
|
||||
bool CommonScript::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 CommonScript::goToAllProducts() {
|
||||
int counter = 100;
|
||||
const QString deviceId = "6edc4a47";
|
||||
const int width = 720;
|
||||
const int height = 1600;
|
||||
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, counter));
|
||||
if (tryToCloseAllBannersOnHomePage(deviceId, width, height)) {
|
||||
QThread::msleep(500);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter));
|
||||
}
|
||||
|
||||
if (xmlScreenParser.isHomeScreen()) {
|
||||
// идем на экран "Все продукты"
|
||||
if (Node allCardTitle = xmlScreenParser.findTextNode("Счета и карты"); !allCardTitle.isEmpty()) {
|
||||
// 1. Если нет кнопки видимой, свайпаем. Кликаем на "Все продукты"
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
Node allProductsBtn = xmlScreenParser.findButtonNode("Все продукты", false);
|
||||
// Node allProductsBtn = xmlScreenParser.findTextNode("Курсы валют и металлов"); для теста
|
||||
if (allProductsBtn.x() > 0 && allProductsBtn.y() > 0) {
|
||||
qDebug() << "--- All Product in focus";
|
||||
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, ++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, ++counter));
|
||||
}
|
||||
|
||||
} else {
|
||||
// пишем в БД, что не дошли до домашней страницы, и завершаемся
|
||||
// сохраняем скрин приложения
|
||||
// дделаем алерт
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Not home screen...";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CommonScript::readTransactionData() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 1. топаем на домашнюю
|
||||
// 2. распарсить мои карты и вернуть данные
|
||||
// 3. синхронизировать историю транзакций (с домашней)
|
||||
|
||||
|
||||
void CommonScript::stop() {
|
||||
m_running = false;
|
||||
}
|
||||
57
android/rshb/CommonScript.h
Normal file
57
android/rshb/CommonScript.h
Normal file
@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include "ScreenXmlParser.h"
|
||||
|
||||
class CommonScript : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CommonScript(QObject *parent = nullptr);
|
||||
|
||||
~CommonScript() override;
|
||||
|
||||
protected:
|
||||
virtual void doStart() = 0;
|
||||
|
||||
// static method
|
||||
static bool inputPinCode(const QString &deviceId, const QList<Node> &pincode);
|
||||
|
||||
bool goToAllProducts();
|
||||
|
||||
Node swipeToButton(
|
||||
const QString &deviceId,
|
||||
const QString &text,
|
||||
int &counter, int width, int height
|
||||
);
|
||||
|
||||
bool runAppAndGoToHomeScreen();
|
||||
|
||||
bool tryToCloseAllBannersOnHomePage(
|
||||
const QString &deviceId, int width, int height
|
||||
);
|
||||
|
||||
bool readTransactionData();
|
||||
|
||||
bool m_running = true;
|
||||
ScreenXmlParser xmlScreenParser;
|
||||
QString m_packageName;
|
||||
QString m_pinCode;
|
||||
int m_width = 0;
|
||||
int m_height = 0;
|
||||
|
||||
signals:
|
||||
void finished();
|
||||
|
||||
public slots:
|
||||
virtual void start() final;
|
||||
|
||||
virtual void stop() final;
|
||||
|
||||
private:
|
||||
// Q_DISABLE_COPY(CommonScript);
|
||||
|
||||
bool goToHomeScreen(
|
||||
const QString &deviceId, const QString &packageName,
|
||||
const QString &pinCode, int width, int height);
|
||||
};
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
#include <QThread>
|
||||
#include <QDebug>
|
||||
#include <QProcess>
|
||||
#include <QRandomGenerator>
|
||||
|
||||
#include "ScreenXmlParser.h"
|
||||
@ -112,6 +113,40 @@ bool HomeScreenScript::goToHomeScreen(
|
||||
}
|
||||
}
|
||||
|
||||
QString getPriceAsString(const double value) {
|
||||
QString sign = value < 0 ? "Минус" : "Плюс";
|
||||
const auto intValue = static_cast<qint64>(std::abs(value)); // округление до целого
|
||||
|
||||
QString number = QString::number(intValue);
|
||||
QString formatted;
|
||||
|
||||
int count = 0;
|
||||
for (int i = number.length() - 1; i >= 0; --i) {
|
||||
formatted.prepend(number[i]);
|
||||
++count;
|
||||
if (count % 3 == 0 && i != 0) {
|
||||
formatted.prepend(' ');
|
||||
}
|
||||
}
|
||||
|
||||
return QString("%1 %2").arg(sign, formatted);
|
||||
}
|
||||
|
||||
Node swipeToButton(
|
||||
ScreenXmlParser &xmlScreenParser, const QString &deviceId,
|
||||
const QString &text, int &counter, const int width, const int height
|
||||
) {
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
if (Node button = xmlScreenParser.findButtonNode(text, true); button.x() > 0 && button.y() > 0) {
|
||||
return button;
|
||||
}
|
||||
const int x = width / 2;
|
||||
const int offset = height / 4;
|
||||
AdbUtils::makeSwipe(deviceId, x, height - offset, x, offset);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++counter));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void HomeScreenScript::start() {
|
||||
// Достаем данные из БД по ИД транзакции, перед тем как запустить мы кладем в БД все данные
|
||||
@ -130,8 +165,53 @@ void HomeScreenScript::start() {
|
||||
const QString bankName = "Альфа-Банк";
|
||||
const QString paySum = "10";
|
||||
|
||||
int index = 0;
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++index));
|
||||
|
||||
// xmlScreenParser.test();
|
||||
// тут просто получаем инфу которую спарсил
|
||||
// == 1. Входящий (TransactionType::Phone)
|
||||
// Перевод через СБП от Егор Иванович Ж (+7(000)-000-00-87) из Озон Банк (Ozon) на 40817810328000022077 .
|
||||
// Идентификатор операции в СБП С2С B5121111443333300170011491301 , Дата операции 01.05.2025 14:14:41 Статус перевода Исполнен
|
||||
// == 2. Исходящий (TransactionType::Phone)
|
||||
// Перевод Надежда Иванкина К** в Сбербанк через СБП, по номеру телефона +79000810046, ID перевода 445900141, дата 03.05.2025
|
||||
|
||||
// (TransactionType::Card) -> T-BANK CARD2CARD
|
||||
// TransactionInfo trInfo = xmlScreenParser.parseTransactionInfoHistory(width, height);
|
||||
// qDebug().noquote().nospace() << convertTransactionToString(trInfo);
|
||||
|
||||
// Если начинается с ЕСПП - то не кликаем еще, ждет обработку
|
||||
// А все переводы по карте - они не начинаются со слова "Перевод" ->
|
||||
// .. пробуем кликать, но если не кликается ???
|
||||
QList<TransactionInfo> transactions = xmlScreenParser.parseTodayAndYesterdayHistory();
|
||||
for (const TransactionInfo &info: transactions) {
|
||||
QString text = QString("%1 %2").arg(info.shortDescription, getPriceAsString(info.amount));
|
||||
qDebug().noquote().nospace() << convertTransactionToString(info);
|
||||
|
||||
// Node trButton = xmlScreenParser.findButtonNode(text, true);
|
||||
Node trButton = swipeToButton(xmlScreenParser, deviceId, text, index, width, height);
|
||||
if (!trButton.isEmpty()) {
|
||||
AdbUtils::makeTap(deviceId, trButton.x(), trButton.y());
|
||||
QThread::msleep(500);
|
||||
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++index));
|
||||
|
||||
Node moreDetailBtn = xmlScreenParser.findButtonNode("Детали операции", false);
|
||||
if (!moreDetailBtn.isEmpty()) {
|
||||
AdbUtils::makeTap(deviceId, moreDetailBtn.x(), moreDetailBtn.y());
|
||||
QThread::msleep(200);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++index));
|
||||
TransactionInfo info = xmlScreenParser.parseTransactionInfoHistory(width, height);
|
||||
qDebug().noquote().nospace() << convertTransactionToString(info);
|
||||
}
|
||||
|
||||
AdbUtils::goBack(deviceId);
|
||||
} else {
|
||||
qWarning() << "--- trButton not found..........";
|
||||
}
|
||||
// Перевод ВЛАДИСЛАВ НИКОЛАЕВИЧ К** в Т-Банк через СБП, по номеру телефона +7985553... Минус 10 рублей 0 копеек
|
||||
}
|
||||
|
||||
xmlScreenParser.test();
|
||||
return;
|
||||
|
||||
int payCounter = 0;
|
||||
@ -294,10 +374,8 @@ void HomeScreenScript::start() {
|
||||
|
||||
if (!paymentCompleted.isEmpty()) {
|
||||
// Если вдруг исполнен
|
||||
|
||||
} else if (!paymentInProgress.isEmpty()) {
|
||||
// Если все еще в ожидании
|
||||
|
||||
} else {
|
||||
qWarning() << "--- paymentCompleted or paymentInProgress not found...";
|
||||
}
|
||||
|
||||
55
android/rshb/PayByPhoneScript.cpp
Normal file
55
android/rshb/PayByPhoneScript.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
#include "PayByPhoneScript.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "DatabaseManager.h"
|
||||
#include "dao/AccountDAO.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
#include "dao/TransactionDAO.h"
|
||||
#include "db/ApplicationInfo.h"
|
||||
#include "db/DeviceInfo.h"
|
||||
|
||||
PayByPhoneScript::PayByPhoneScript(
|
||||
AccountInfo account,
|
||||
QString phone,
|
||||
QString bankName,
|
||||
const double amount,
|
||||
QObject *parent
|
||||
) : CommonScript(parent),
|
||||
m_account(std::move(account)), m_phone(std::move(phone)),
|
||||
m_bankName(std::move(bankName)), m_amount(amount) {
|
||||
}
|
||||
|
||||
PayByPhoneScript::~PayByPhoneScript() = default;
|
||||
|
||||
void PayByPhoneScript::doStart() {
|
||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_account.deviceId);
|
||||
const ApplicationInfo app = ApplicationDAO::getApplicationsByDeviceId(m_account.deviceId, m_account.appName);
|
||||
|
||||
if (device.id.isEmpty() || app.id.isEmpty()) {
|
||||
qDebug() << "Device or app not found....";
|
||||
return;
|
||||
}
|
||||
m_width = device.screenWidth;
|
||||
m_height = device.screenHeight;
|
||||
m_pinCode = app.pinCode;
|
||||
m_packageName = app.package;
|
||||
|
||||
if (runAppAndGoToHomeScreen()) {
|
||||
if (goToAllProducts()) {
|
||||
QList<AccountInfo> accounts = xmlScreenParser.parseAccountsInfo();
|
||||
for (AccountInfo &account: accounts) {
|
||||
account.deviceId = m_account.deviceId;
|
||||
account.appName = m_account.appName;
|
||||
if (!AccountDAO::upsertAccount(account)) {
|
||||
qDebug() << "Not updated: " << account.lastNumbers;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Не смогли открыть 'Все продукты'";
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Не смогли дойти до домашней страницы....";
|
||||
}
|
||||
}
|
||||
27
android/rshb/PayByPhoneScript.h
Normal file
27
android/rshb/PayByPhoneScript.h
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "CommonScript.h"
|
||||
|
||||
class PayByPhoneScript final : public CommonScript {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PayByPhoneScript(
|
||||
AccountInfo account,
|
||||
QString phone,
|
||||
QString bankName,
|
||||
double amount,
|
||||
QObject *parent = nullptr
|
||||
);
|
||||
|
||||
~PayByPhoneScript() override;
|
||||
|
||||
protected:
|
||||
void doStart() override;
|
||||
|
||||
private:
|
||||
const AccountInfo m_account;
|
||||
const QString m_phone;
|
||||
const QString m_bankName;
|
||||
const double m_amount;
|
||||
};
|
||||
@ -12,6 +12,7 @@
|
||||
|
||||
#include "adb/AdbUtils.h"
|
||||
#include "android/xml/Node.h"
|
||||
#include "db/AccountInfo.h"
|
||||
#include "db/TransactionInfo.h"
|
||||
|
||||
ScreenXmlParser::ScreenXmlParser(QObject *parent) : QObject(parent) {
|
||||
@ -21,6 +22,7 @@ ScreenXmlParser::~ScreenXmlParser() = 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");
|
||||
@ -31,6 +33,8 @@ static const QRegularExpression reDate(R"((?:Дата операции|дата)
|
||||
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 ScreenXmlParser::tryToFindBannerOrDialog(const int width, const int height) {
|
||||
// 1. Ищем push banner - он перекрывает весь экран
|
||||
Node first = m_nodes[0];
|
||||
@ -106,7 +110,6 @@ Node ScreenXmlParser::findFirstEditText() {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
Node ScreenXmlParser::findButtonNode(const QString &text, const bool contains) {
|
||||
for (const Node &node: m_nodes) {
|
||||
if (contains) {
|
||||
@ -122,7 +125,6 @@ Node ScreenXmlParser::findButtonNode(const QString &text, const bool contains) {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
bool ScreenXmlParser::isHomeScreen() {
|
||||
// Есть заголовок "Счета и карты" и кнопка "Все продукты"
|
||||
Node text;
|
||||
@ -268,17 +270,25 @@ QString getMonthAsString(const int month) {
|
||||
}
|
||||
}
|
||||
|
||||
QString findSubInfo(const QDomElement &elem) {
|
||||
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") == "1") {
|
||||
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 "";
|
||||
}
|
||||
|
||||
@ -347,19 +357,19 @@ void traverseNodes(const QDomNode &node) {
|
||||
QString index = infoElement.attribute("index");
|
||||
switch (index.toInt()) {
|
||||
case 2:
|
||||
qDebug() << "Дата операции:" << findSubInfo(infoElement);
|
||||
continue;
|
||||
qDebug() << "Дата операции:" << findSubInfo(infoElement, "1");
|
||||
break;
|
||||
case 5:
|
||||
qDebug() << "Название:" << findSubInfo(infoElement);
|
||||
qDebug() << "Название:" << findSubInfo(infoElement, "1");
|
||||
qDebug() << "-------------------------------------";
|
||||
// parseTransfer(findSubInfo(infoElement));
|
||||
continue;
|
||||
break;
|
||||
case 7:
|
||||
qDebug() << "Комиссия:" << findSubInfo(infoElement);;
|
||||
continue;
|
||||
qDebug() << "Комиссия:" << findSubInfo(infoElement, "1");
|
||||
break;
|
||||
default:
|
||||
qDebug() << "Прочее:" << findSubInfo(infoElement);;
|
||||
continue;
|
||||
qDebug() << "Прочее:" << findSubInfo(infoElement, "1");
|
||||
break;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -661,15 +671,15 @@ TransactionInfo parseExpandedInfoHistory(const QDomElement &root) {
|
||||
switch (QDomElement e = info.toElement(); e.attribute("index").toInt()) {
|
||||
case 2:
|
||||
// Дата операции
|
||||
transaction.bankTime = findSubInfo(e);
|
||||
transaction.bankTime = findSubInfo(e, "1");
|
||||
break;
|
||||
case 5:
|
||||
transaction.description = findSubInfo(e);
|
||||
transaction.description = findSubInfo(e, "1");
|
||||
parseTransferInfo(transaction.description, transaction);
|
||||
break;
|
||||
case 7:
|
||||
// Комиссия
|
||||
transaction.fee = findSubInfo(e).remove(onlyDigitsRegex).toFloat();
|
||||
transaction.fee = findSubInfo(e, "1").remove(onlyDigitsRegex).toFloat();
|
||||
break;
|
||||
default:
|
||||
// qDebug() << "Прочее:" << findSubInfo(e);
|
||||
@ -705,9 +715,90 @@ TransactionInfo ScreenXmlParser::parseTransactionInfoHistory(int screenWidth, in
|
||||
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 = 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.trExternalId = 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 {};
|
||||
}
|
||||
|
||||
QList<AccountInfo> ScreenXmlParser::parseAccountsInfo() {
|
||||
QList<AccountInfo> accounts;
|
||||
for (const Node &node: m_nodes) {
|
||||
if (node.text.contains("Номер карты с последними цифрами")) {
|
||||
if (QRegularExpressionMatch match = reCardData.match(node.text); match.hasMatch()) {
|
||||
AccountInfo info;
|
||||
info.lastNumbers = match.captured(1).remove(" ");
|
||||
info.amount = match.captured(2).remove(" ").toFloat();
|
||||
info.description = node.text.mid(0, node.text.indexOf("Номер карты"));
|
||||
info.updateTime = QDateTime::currentDateTime();
|
||||
accounts.append(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return accounts;
|
||||
}
|
||||
|
||||
void ScreenXmlParser::test() {
|
||||
return;
|
||||
QFile file("assets/rshb/test/spb_test_send.xml");
|
||||
// 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";
|
||||
}
|
||||
@ -720,6 +811,13 @@ void ScreenXmlParser::test() {
|
||||
|
||||
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);
|
||||
// ЕСПП
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include <QObject>
|
||||
#include <QDomElement>
|
||||
#include "android/xml/Node.h"
|
||||
#include "db/AccountInfo.h"
|
||||
#include "db/TransactionInfo.h"
|
||||
|
||||
class ScreenXmlParser final : public QObject {
|
||||
@ -36,6 +37,8 @@ public:
|
||||
TransactionInfo parseTransactionInfoHistory(int screenWidth, int screenHeight);
|
||||
QList<TransactionInfo> parseTodayAndYesterdayHistory();
|
||||
|
||||
QList<AccountInfo> parseAccountsInfo();
|
||||
|
||||
void test();
|
||||
|
||||
private:
|
||||
|
||||
@ -1,26 +1,64 @@
|
||||
#include "DatabaseManager.h"
|
||||
#include <QCoreApplication>
|
||||
#include <QSqlError>
|
||||
#include <QtCore/qcoreapplication.h>
|
||||
#include <QDebug>
|
||||
#include <QThread>
|
||||
|
||||
DatabaseManager::DatabaseManager() {
|
||||
db = QSqlDatabase::addDatabase("QSQLITE");
|
||||
db.setDatabaseName(QCoreApplication::applicationDirPath() + "/database/app_data.db");
|
||||
if (!db.open()) {
|
||||
qWarning() << "Ошибка открытия базы данных:" << db.lastError().text();
|
||||
}
|
||||
}
|
||||
DatabaseManager::DatabaseManager() = default;
|
||||
|
||||
DatabaseManager::~DatabaseManager() {
|
||||
if (db.isOpen()) {
|
||||
db.close();
|
||||
QMutexLocker locker(&mutex);
|
||||
|
||||
for (auto it = connectionPool.begin(); it != connectionPool.end(); ++it) {
|
||||
QSqlDatabase::removeDatabase(it.key());
|
||||
}
|
||||
qDebug() << "Все соединения закрыты";
|
||||
}
|
||||
|
||||
DatabaseManager& DatabaseManager::instance() {
|
||||
DatabaseManager &DatabaseManager::instance() {
|
||||
static DatabaseManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
QSqlDatabase& DatabaseManager::database() {
|
||||
return db;
|
||||
QSqlDatabase DatabaseManager::database() {
|
||||
QMutexLocker locker(&mutex);
|
||||
QString name = connectionName();
|
||||
|
||||
// Создаем новое соединение, если его ещё нет
|
||||
if (!connectionPool.contains(name)) {
|
||||
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", name);
|
||||
db.setDatabaseName(QCoreApplication::applicationDirPath() + "/database/app_data.db");
|
||||
|
||||
if (!db.open()) {
|
||||
qWarning() << "Ошибка открытия базы данных:" << db.lastError().text();
|
||||
}
|
||||
|
||||
connectionPool[name] = db;
|
||||
|
||||
// Автоматическое удаление соединения при завершении потока
|
||||
QObject::connect(QThread::currentThread(), &QThread::finished, [this, name]() {
|
||||
QMutexLocker cLocker(&mutex);
|
||||
if (connectionPool.contains(name)) {
|
||||
connectionPool.remove(name);
|
||||
QSqlDatabase::removeDatabase(name);
|
||||
qDebug() << "Соединение" << name << "удалено";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return connectionPool[name];
|
||||
}
|
||||
|
||||
QString DatabaseManager::connectionName() {
|
||||
return QString("connection_%1").arg(reinterpret_cast<quintptr>(QThread::currentThreadId()));
|
||||
}
|
||||
|
||||
|
||||
void DatabaseManager::closeConnection() {
|
||||
QMutexLocker locker(&mutex);
|
||||
if (const QString name = connectionName(); connectionPool.contains(name)) {
|
||||
connectionPool.remove(name);
|
||||
QSqlDatabase::removeDatabase(name);
|
||||
qDebug() << "Соединение" << name << "удалено вручную";
|
||||
}
|
||||
}
|
||||
@ -1,16 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <QSqlDatabase>
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
#include <QMutex>
|
||||
#include <QSet>
|
||||
|
||||
class DatabaseManager final : public QObject {
|
||||
Q_OBJECT
|
||||
class DatabaseManager {
|
||||
public:
|
||||
static DatabaseManager& instance();
|
||||
|
||||
QSqlDatabase& database();
|
||||
QSqlDatabase database();
|
||||
void closeConnection();
|
||||
|
||||
private:
|
||||
DatabaseManager();
|
||||
~DatabaseManager() override;
|
||||
QSqlDatabase db;
|
||||
~DatabaseManager();
|
||||
|
||||
static QString connectionName();
|
||||
|
||||
QMutex mutex;
|
||||
QSet<QString> activeConnections;
|
||||
QMap<QString, QSqlDatabase> connectionPool;
|
||||
};
|
||||
Binary file not shown.
@ -9,9 +9,9 @@ bool AccountDAO::upsertAccount(const AccountInfo &info) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
INSERT INTO accounts (device_id, app_name, card_name, amount, update_time, status, description)
|
||||
VALUES (:device_id, :app_name, :card_name, :amount, :update_time, :status, :description)
|
||||
ON CONFLICT(device_id, app_name, card_name)
|
||||
INSERT INTO accounts (device_id, app_name, numbers, last_numbers, amount, update_time, status, description)
|
||||
VALUES (:device_id, :app_name, :numbers, :last_numbers, :amount, :update_time, :status, :description)
|
||||
ON CONFLICT(device_id, app_name, last_numbers)
|
||||
DO UPDATE SET
|
||||
amount = excluded.amount,
|
||||
update_time = excluded.update_time,
|
||||
@ -20,10 +20,11 @@ bool AccountDAO::upsertAccount(const AccountInfo &info) {
|
||||
|
||||
query.bindValue(":device_id", info.deviceId);
|
||||
query.bindValue(":app_name", info.appName);
|
||||
query.bindValue(":card_name", info.cardName);
|
||||
query.bindValue(":numbers", info.numbers);
|
||||
query.bindValue(":last_numbers", info.lastNumbers);
|
||||
query.bindValue(":amount", info.amount);
|
||||
query.bindValue(":update_time", info.updateTime.toString(Qt::ISODate));
|
||||
query.bindValue(":status", info.status);
|
||||
query.bindValue(":status", accountStatusToString(info.status));
|
||||
query.bindValue(":description", info.description);
|
||||
|
||||
if (!query.exec()) {
|
||||
@ -46,7 +47,7 @@ QList<AccountInfo> AccountDAO::getAccounts(const QString &deviceId, const QStrin
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, device_id, app_name, card_name, amount, update_time, status, description
|
||||
SELECT id, device_id, app_name, numbers, last_numbers, amount, update_time, status, description
|
||||
FROM accounts
|
||||
WHERE device_id = :device_id AND app_name = :app_name
|
||||
)");
|
||||
@ -64,10 +65,11 @@ QList<AccountInfo> AccountDAO::getAccounts(const QString &deviceId, const QStrin
|
||||
acc.id = query.value("id").toInt();
|
||||
acc.deviceId = query.value("device_id").toString();
|
||||
acc.appName = query.value("app_name").toString();
|
||||
acc.cardName = query.value("card_name").toString();
|
||||
acc.numbers = query.value("numbers").toString();
|
||||
acc.lastNumbers = query.value("last_numbers").toString();
|
||||
acc.amount = query.value("amount").toDouble();
|
||||
acc.updateTime = QDateTime::fromString(query.value("update_time").toString(), Qt::ISODate);
|
||||
acc.status = query.value("status").toString();
|
||||
acc.status = accountStatusFromString(query.value("status").toString());
|
||||
acc.description = query.value("description").toString();
|
||||
accounts.append(acc);
|
||||
}
|
||||
@ -75,12 +77,25 @@ QList<AccountInfo> AccountDAO::getAccounts(const QString &deviceId, const QStrin
|
||||
return accounts;
|
||||
}
|
||||
|
||||
AccountInfo AccountDAO::getAccountById(const int accountId) {
|
||||
AccountInfo parseAccount(const QSqlQuery &query) {
|
||||
AccountInfo acc;
|
||||
acc.id = query.value("id").toInt();
|
||||
acc.deviceId = query.value("device_id").toString();
|
||||
acc.appName = query.value("app_name").toString();
|
||||
acc.lastNumbers = query.value("last_numbers").toString();
|
||||
acc.numbers = query.value("numbers").toString();
|
||||
acc.amount = query.value("amount").toDouble();
|
||||
acc.updateTime = QDateTime::fromString(query.value("update_time").toString(), Qt::ISODate);
|
||||
acc.status = accountStatusFromString(query.value("status").toString());
|
||||
acc.description = query.value("description").toString();
|
||||
return acc;
|
||||
}
|
||||
|
||||
AccountInfo AccountDAO::getAccountById(const int accountId) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, device_id, app_name, card_name, amount, update_time, status, description
|
||||
SELECT id, device_id, app_name, numbers, last_numbers, amount, update_time, status, description
|
||||
FROM accounts
|
||||
WHERE id = :id
|
||||
LIMIT 1
|
||||
@ -90,19 +105,36 @@ AccountInfo AccountDAO::getAccountById(const int accountId) {
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при получении account по ID:" << query.lastError().text();
|
||||
return acc;
|
||||
return {};
|
||||
}
|
||||
|
||||
if (query.next()) {
|
||||
acc.id = query.value("id").toInt();
|
||||
acc.deviceId = query.value("device_id").toString();
|
||||
acc.appName = query.value("app_name").toString();
|
||||
acc.cardName = query.value("card_name").toString();
|
||||
acc.amount = query.value("amount").toDouble();
|
||||
acc.updateTime = QDateTime::fromString(query.value("update_time").toString(), Qt::ISODate);
|
||||
acc.status = query.value("status").toString();
|
||||
acc.description = query.value("description").toString();
|
||||
return parseAccount(query);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
AccountInfo AccountDAO::findAppAccount(const QString &appName, const QString &cardNumber) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, device_id, app_name, numbers, last_numbers, amount, update_time, status, description
|
||||
FROM accounts
|
||||
WHERE app_name = :app_name AND numbers = :numbers
|
||||
LIMIT 1
|
||||
)");
|
||||
|
||||
query.bindValue(":app_name", appName);
|
||||
query.bindValue(":numbers", cardNumber);
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при получении account по ID:" << query.lastError().text();
|
||||
return {};
|
||||
}
|
||||
|
||||
return acc;
|
||||
if (query.next()) {
|
||||
return parseAccount(query);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -10,4 +10,6 @@ public:
|
||||
[[nodiscard]] static QList<AccountInfo> getAccounts(const QString &deviceId, const QString &appName);
|
||||
|
||||
[[nodiscard]] static AccountInfo getAccountById(int accountId);
|
||||
|
||||
[[nodiscard]] static AccountInfo findAppAccount(const QString &appName, const QString &cardNumber);
|
||||
};
|
||||
|
||||
94
database/dao/ApplicationDAO.cpp
Normal file
94
database/dao/ApplicationDAO.cpp
Normal file
@ -0,0 +1,94 @@
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlError>
|
||||
#include <QVariant>
|
||||
#include "ApplicationDAO.h"
|
||||
#include "DatabaseManager.h"
|
||||
#include "db/ApplicationInfo.h"
|
||||
|
||||
bool ApplicationDAO::upsertApplication(const ApplicationInfo &info) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
INSERT INTO applications (id, device_id, pin_code, name, package, status, comment, image)
|
||||
VALUES (:id, :device_id, :pin_code, :name, :package, :status, :comment, :image)
|
||||
ON CONFLICT(device_id, name)
|
||||
DO UPDATE SET
|
||||
device_id = excluded.device_id,
|
||||
pin_code = excluded.pin_code,
|
||||
name = excluded.name,
|
||||
package = excluded.package,
|
||||
status = excluded.status,
|
||||
comment = excluded.comment,
|
||||
image = excluded.image
|
||||
)");
|
||||
|
||||
query.bindValue(":id", info.id);
|
||||
query.bindValue(":device_id", info.deviceId);
|
||||
query.bindValue(":pin_code", info.pinCode);
|
||||
query.bindValue(":name", info.name);
|
||||
query.bindValue(":package", info.package);
|
||||
query.bindValue(":status", info.status);
|
||||
query.bindValue(":comment", info.comment);
|
||||
query.bindValue(":image", info.image);
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при вставке/обновлении application:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ApplicationDAO::updatePinCode(const QString &appId, const QString &pinCode) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
UPDATE applications
|
||||
SET pin_code = :pin_code
|
||||
WHERE id = :id
|
||||
)");
|
||||
|
||||
query.bindValue(":pin_code", pinCode);
|
||||
query.bindValue(":id", appId);
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при обновлении PIN-кода приложения:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ApplicationInfo ApplicationDAO::getApplicationsByDeviceId(const QString &deviceId, const QString &appName) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, device_id, pin_code, name, package, status, comment, image
|
||||
FROM applications
|
||||
WHERE device_id = :device_id AND name = :name
|
||||
LIMIT 1
|
||||
)");
|
||||
|
||||
query.bindValue(":device_id", deviceId);
|
||||
query.bindValue(":name", appName);
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при получении приложений по deviceId:" << query.lastError().text();
|
||||
return {};
|
||||
}
|
||||
|
||||
while (query.next()) {
|
||||
ApplicationInfo app;
|
||||
app.id = query.value("id").toString();
|
||||
app.deviceId = query.value("device_id").toString();
|
||||
app.pinCode = query.value("pin_code").toString();
|
||||
app.name = query.value("name").toString();
|
||||
app.package = query.value("package").toString();
|
||||
app.status = query.value("status").toString();
|
||||
app.comment = query.value("comment").toString();
|
||||
app.image = query.value("image").toByteArray();
|
||||
return app;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
12
database/dao/ApplicationDAO.h
Normal file
12
database/dao/ApplicationDAO.h
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "db/ApplicationInfo.h"
|
||||
|
||||
class ApplicationDAO {
|
||||
public:
|
||||
[[nodiscard]] static bool upsertApplication(const ApplicationInfo &info);
|
||||
|
||||
[[nodiscard]] static bool updatePinCode(const QString &appId, const QString &pinCode);
|
||||
|
||||
[[nodiscard]] static ApplicationInfo getApplicationsByDeviceId(const QString &deviceId, const QString &appName);
|
||||
};
|
||||
@ -4,6 +4,20 @@
|
||||
#include "DatabaseManager.h"
|
||||
#include "db/DeviceInfo.h"
|
||||
|
||||
DeviceInfo parseDevice(const QSqlQuery &query) {
|
||||
DeviceInfo device;
|
||||
device.id = query.value("id").toString();
|
||||
device.status = query.value("status").toString();
|
||||
device.name = query.value("name").toString();
|
||||
device.android = query.value("android").toString();
|
||||
device.screenWidth = query.value("screen_width").toInt();
|
||||
device.screenHeight = query.value("screen_height").toInt();
|
||||
device.battery = query.value("battery").toInt();
|
||||
device.updateTime = QDateTime::fromString(query.value("update_time").toString(), Qt::ISODate);
|
||||
device.image = query.value("image").toByteArray();
|
||||
return device;
|
||||
}
|
||||
|
||||
QList<DeviceInfo> DeviceDAO::getAllDevices(const bool online) {
|
||||
QList<DeviceInfo> devices;
|
||||
|
||||
@ -24,23 +38,34 @@ QList<DeviceInfo> DeviceDAO::getAllDevices(const bool online) {
|
||||
}
|
||||
|
||||
while (query.next()) {
|
||||
DeviceInfo device;
|
||||
device.id = query.value("id").toString();
|
||||
device.status = query.value("status").toString();
|
||||
device.name = query.value("name").toString();
|
||||
device.android = query.value("android").toString();
|
||||
device.screenWidth = query.value("screen_width").toInt();
|
||||
device.screenHeight = query.value("screen_height").toInt();
|
||||
device.battery = query.value("battery").toInt();
|
||||
device.updateTime = QDateTime::fromString(query.value("update_time").toString(), Qt::ISODate);
|
||||
device.image = query.value("image").toByteArray();
|
||||
|
||||
devices.append(device);
|
||||
devices.append(parseDevice(query));
|
||||
}
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
DeviceInfo DeviceDAO::getDeviceById(const QString &deviceId) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, status, name, android, screen_width, screen_height, battery, update_time, image
|
||||
FROM devices
|
||||
WHERE id = :deviceId
|
||||
)");
|
||||
query.bindValue(":deviceId", deviceId);
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при получении устройства:" << query.lastError().text();
|
||||
return {};
|
||||
}
|
||||
|
||||
while (query.next()) {
|
||||
return parseDevice(query);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool DeviceDAO::upsertDevice(const DeviceInfo &device) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
|
||||
class DeviceDAO {
|
||||
public:
|
||||
[[nodiscard]] static DeviceInfo getDeviceById(const QString &deviceId);
|
||||
|
||||
[[nodiscard]] static bool upsertDevice(const DeviceInfo &device);
|
||||
|
||||
[[nodiscard]] static bool markDevicesOfflineExcept(const QStringList &onlineDeviceIds);
|
||||
|
||||
31
main.cpp
31
main.cpp
@ -16,6 +16,7 @@
|
||||
#include <chrono>
|
||||
|
||||
#include "rshb/HomeScreenScript.h"
|
||||
#include "rshb/PayByPhoneScript.h"
|
||||
|
||||
void setupWorkerAndThread(QCoreApplication &app) {
|
||||
auto *thread = new QThread;
|
||||
@ -48,19 +49,22 @@ void setupWorkerAndThread(QCoreApplication &app) {
|
||||
}
|
||||
|
||||
void setupScriptAutoPaymentAndThread(QCoreApplication &app) {
|
||||
auto *thread = new QThread;
|
||||
auto *scriptWorker = new HomeScreenScript(0);
|
||||
const QString appName = "rshb";
|
||||
const QString cardNumber = "2200380317107183";
|
||||
|
||||
const QString phoneNumber = "79641815146";
|
||||
const QString bankName = "Альфа-Банк";
|
||||
const double ammount = 250;
|
||||
|
||||
AccountInfo account = AccountDAO::findAppAccount(appName, cardNumber);
|
||||
if (account.id != -1) {
|
||||
auto *thread = new QThread;
|
||||
auto *scriptWorker = new PayByPhoneScript(account, phoneNumber, bankName, ammount);
|
||||
|
||||
// Перемещаем worker в новый поток thread.
|
||||
// Это значит, что все слоты worker, вызываемые через connect(), теперь будут исполняться в этом фоновом потоке.
|
||||
scriptWorker->moveToThread(thread);
|
||||
// Когда поток стартует, запускаем start()
|
||||
QObject::connect(thread, &QThread::started, scriptWorker, &HomeScreenScript::start);
|
||||
// Когда работа (worker) завершена (emit finished()), останавливаем поток
|
||||
QObject::connect(scriptWorker, &HomeScreenScript::finished, thread, &QThread::quit);
|
||||
// Удаляем работу (worker) (deleteLater)
|
||||
QObject::connect(scriptWorker, &HomeScreenScript::finished, scriptWorker, &QObject::deleteLater);
|
||||
// Удаляем поток (deleteLater)
|
||||
QObject::connect(thread, &QThread::started, scriptWorker, &PayByPhoneScript::start);
|
||||
QObject::connect(scriptWorker, &PayByPhoneScript::finished, thread, &QThread::quit);
|
||||
QObject::connect(scriptWorker, &PayByPhoneScript::finished, scriptWorker, &QObject::deleteLater);
|
||||
QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
|
||||
|
||||
QObject::connect(&app, &QCoreApplication::aboutToQuit, [=]() {
|
||||
@ -68,6 +72,7 @@ void setupScriptAutoPaymentAndThread(QCoreApplication &app) {
|
||||
});
|
||||
|
||||
thread->start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -98,7 +103,7 @@ int main(int argc, char *argv[]) {
|
||||
// qDebug() << "Сохранившееся время:" << savedTime.toString();
|
||||
//
|
||||
//
|
||||
MainWindow window;
|
||||
window.show();
|
||||
// MainWindow window;
|
||||
// window.show();
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
@ -18,10 +18,12 @@ CREATE TABLE IF NOT EXISTS applications
|
||||
device_id TEXT,
|
||||
pin_code TEXT,
|
||||
name TEXT,
|
||||
package TEXT,
|
||||
status TEXT,
|
||||
comment TEXT,
|
||||
image BLOB,
|
||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE
|
||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
||||
UNIQUE (device_id, name)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS accounts
|
||||
@ -29,13 +31,14 @@ CREATE TABLE IF NOT EXISTS accounts
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
device_id TEXT,
|
||||
app_name TEXT,
|
||||
card_name TEXT,
|
||||
numbers TEXT,
|
||||
last_numbers TEXT,
|
||||
description TEXT,
|
||||
amount REAL,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
status TEXT,
|
||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
||||
UNIQUE (device_id, app_name, card_name)
|
||||
UNIQUE (device_id, app_name, last_numbers)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS transactions
|
||||
|
||||
@ -2,13 +2,58 @@
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
enum class AccountStatus {
|
||||
Active,
|
||||
Blocked
|
||||
};
|
||||
|
||||
inline QString accountStatusToString(const AccountStatus status) {
|
||||
switch (status) {
|
||||
case AccountStatus::Active: return "active";
|
||||
case AccountStatus::Blocked: return "blocked";
|
||||
default: return "active";
|
||||
}
|
||||
}
|
||||
|
||||
inline AccountStatus accountStatusFromString(const QString &str) {
|
||||
if (str == "active") return AccountStatus::Active;
|
||||
if (str == "blocked") return AccountStatus::Blocked;
|
||||
return AccountStatus::Active;
|
||||
}
|
||||
|
||||
struct AccountInfo {
|
||||
int id;
|
||||
int id = -1;
|
||||
QString deviceId;
|
||||
QString appName;
|
||||
QString cardName;
|
||||
double amount;
|
||||
QString lastNumbers;
|
||||
QString numbers;
|
||||
double amount = 0.0;
|
||||
QDateTime updateTime;
|
||||
QString status;
|
||||
AccountStatus status = AccountStatus::Active;
|
||||
QString description;
|
||||
};
|
||||
|
||||
inline QString convertAccountToString(const AccountInfo &info) {
|
||||
QStringList lines;
|
||||
QStringList emptyFields;
|
||||
|
||||
lines << "AccountInfo {";
|
||||
|
||||
if (info.id != -1) lines << " id: " + QString::number(info.id); else emptyFields << "id";
|
||||
if (!info.deviceId.isEmpty()) lines << " deviceId: " + info.deviceId; else emptyFields << "deviceId";
|
||||
if (!info.appName.isEmpty()) lines << " appName: " + info.appName; else emptyFields << "appName";
|
||||
if (!info.numbers.isEmpty()) lines << " numbers: " + info.numbers; else emptyFields << "numbers";
|
||||
if (!info.lastNumbers.isEmpty()) lines << " lastNumbers: " + info.lastNumbers; else emptyFields << "lastNumbers";
|
||||
if (info.amount != 0.0) lines << " amount: " + QString::number(info.amount, 'f', 2); else emptyFields << "amount";
|
||||
if (info.updateTime.isValid()) lines << " updateTime: " + info.updateTime.toString(Qt::ISODate); else emptyFields << "updateTime";
|
||||
if (info.status != AccountStatus::Active) lines << " status: " + accountStatusToString(info.status); else emptyFields << "status";
|
||||
if (!info.description.isEmpty()) lines << " description: " + info.description; else emptyFields << "description";
|
||||
|
||||
if (!emptyFields.isEmpty()) {
|
||||
lines << " --- empty: " + emptyFields.join(", ");
|
||||
}
|
||||
|
||||
lines << "}";
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
17
models/db/ApplicationInfo.h
Normal file
17
models/db/ApplicationInfo.h
Normal file
@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QByteArray>
|
||||
#include <QDateTime>
|
||||
|
||||
struct ApplicationInfo {
|
||||
QString id;
|
||||
QString deviceId;
|
||||
QString pinCode;
|
||||
QString name;
|
||||
QString package;
|
||||
QString status;
|
||||
QString comment;
|
||||
QByteArray image;
|
||||
QDateTime updateTime;
|
||||
};
|
||||
@ -7,9 +7,9 @@ struct DeviceInfo {
|
||||
QString status;
|
||||
QString name;
|
||||
QString android;
|
||||
int screenWidth;
|
||||
int screenHeight;
|
||||
int battery;
|
||||
int screenWidth = 0;
|
||||
int screenHeight = 0;
|
||||
int battery = 0;
|
||||
QDateTime updateTime;
|
||||
QByteArray image;
|
||||
};
|
||||
@ -71,43 +71,34 @@ struct TransactionInfo {
|
||||
};
|
||||
|
||||
inline QString convertTransactionToString(const TransactionInfo &tx) {
|
||||
return QString(
|
||||
"TransactionInfo {\n"
|
||||
" id: %1\n"
|
||||
" accountId: %2\n"
|
||||
" amount: %3\n"
|
||||
" fee: %4\n"
|
||||
" status: %5\n"
|
||||
" type: %6\n"
|
||||
" phone: %7\n"
|
||||
" shortDescription: %8\n"
|
||||
" updatedShortDescription: %9\n"
|
||||
" description: %10\n"
|
||||
" updatedDescription: %11\n"
|
||||
" bankName: %12\n"
|
||||
" bankTime: %13\n"
|
||||
" name: %14\n"
|
||||
" trExternalId: %15\n"
|
||||
" updateTime: %16\n"
|
||||
" completeTime: %17\n"
|
||||
" timestamp: %18\n"
|
||||
"}"
|
||||
).arg(tx.id)
|
||||
.arg(tx.accountId)
|
||||
.arg(tx.amount)
|
||||
.arg(tx.fee)
|
||||
.arg(transactionStatusToString(tx.status))
|
||||
.arg(transactionTypeToString(tx.type))
|
||||
.arg(tx.phone)
|
||||
.arg(tx.shortDescription)
|
||||
.arg(tx.updatedShortDescription)
|
||||
.arg(tx.description)
|
||||
.arg(tx.updatedDescription)
|
||||
.arg(tx.bankName)
|
||||
.arg(tx.bankTime)
|
||||
.arg(tx.name)
|
||||
.arg(tx.trExternalId)
|
||||
.arg(tx.updateTime.toString(Qt::ISODate))
|
||||
.arg(tx.completeTime.toString(Qt::ISODate))
|
||||
.arg(tx.timestamp.toString(Qt::ISODate));
|
||||
QStringList lines;
|
||||
QStringList emptyFields;
|
||||
|
||||
lines << "TransactionInfo {";
|
||||
|
||||
if (tx.id != -1) lines << " id: " + QString::number(tx.id); else emptyFields << "id";
|
||||
if (tx.accountId != -1) lines << " accountId: " + QString::number(tx.accountId); else emptyFields << "accountId";
|
||||
if (tx.amount != 0.0) lines << " amount: " + QString::number(tx.amount); else emptyFields << "amount";
|
||||
if (tx.fee != 0.0) lines << " fee: " + QString::number(tx.fee); else emptyFields << "fee";
|
||||
if (tx.status != TransactionStatus::Unknown) lines << " status: " + transactionStatusToString(tx.status); else emptyFields << "status";
|
||||
if (tx.type != TransactionType::Unknown) lines << " type: " + transactionTypeToString(tx.type); else emptyFields << "type";
|
||||
if (!tx.phone.isEmpty()) lines << " phone: " + tx.phone; else emptyFields << "phone";
|
||||
if (!tx.shortDescription.isEmpty()) lines << " shortDescription: " + tx.shortDescription; else emptyFields << "shortDescription";
|
||||
if (!tx.updatedShortDescription.isEmpty()) lines << " updatedShortDescription: " + tx.updatedShortDescription; else emptyFields << "updatedShortDescription";
|
||||
if (!tx.description.isEmpty()) lines << " description: " + tx.description; else emptyFields << "description";
|
||||
if (!tx.updatedDescription.isEmpty()) lines << " updatedDescription: " + tx.updatedDescription; else emptyFields << "updatedDescription";
|
||||
if (!tx.bankName.isEmpty()) lines << " bankName: " + tx.bankName; else emptyFields << "bankName";
|
||||
if (!tx.bankTime.isEmpty()) lines << " bankTime: " + tx.bankTime; else emptyFields << "bankTime";
|
||||
if (!tx.name.isEmpty()) lines << " name: " + tx.name; else emptyFields << "name";
|
||||
if (!tx.trExternalId.isEmpty()) lines << " trExternalId: " + tx.trExternalId; else emptyFields << "trExternalId";
|
||||
if (tx.updateTime.isValid()) lines << " updateTime: " + tx.updateTime.toString(Qt::ISODate); else emptyFields << "updateTime";
|
||||
if (tx.completeTime.isValid()) lines << " completeTime: " + tx.completeTime.toString(Qt::ISODate); else emptyFields << "completeTime";
|
||||
if (tx.timestamp.isValid()) lines << " timestamp: " + tx.timestamp.toString(Qt::ISODate); else emptyFields << "timestamp";
|
||||
|
||||
if (!emptyFields.isEmpty()) {
|
||||
lines << " --- empty: " + emptyFields.join(", ");
|
||||
}
|
||||
lines << "}";
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
@ -55,13 +55,13 @@ AccountWindow::AccountWindow(QWidget *parent): QWidget(parent) {
|
||||
|
||||
for (int k = 0; k < accounts.size(); ++k) {
|
||||
AccountInfo account = accounts[k];
|
||||
QTableWidgetItem *item = new QTableWidgetItem("*" + account.cardName);
|
||||
QTableWidgetItem *item = new QTableWidgetItem("*" + account.lastNumbers);
|
||||
item->setData(Qt::UserRole, account.id);
|
||||
tableWidget->setItem(k, 0, item);
|
||||
tableWidget->setItem(k, 1, new QTableWidgetItem(QString::number(account.amount)));
|
||||
tableWidget->setItem(k, 2, new QTableWidgetItem(account.updateTime.toString("dd.MM.yyyy HH:mm:ss")));
|
||||
tableWidget->setItem(k, 3, new QTableWidgetItem(account.description));
|
||||
tableWidget->setItem(k, 4, new QTableWidgetItem(account.status));
|
||||
tableWidget->setItem(k, 4, new QTableWidgetItem(accountStatusToString(account.status)));
|
||||
totalHeight += tableWidget->rowHeight(k);
|
||||
}
|
||||
tableWidget->setFixedHeight(totalHeight + 2);
|
||||
|
||||
@ -14,7 +14,7 @@ TransactionWindow::TransactionWindow(const int accountId, QWidget *parent): QDia
|
||||
|
||||
AccountInfo account = AccountDAO::getAccountById(accountId);
|
||||
|
||||
QLabel *label = new QLabel(QString("*%1 | %2").arg(account.cardName, account.description), this);
|
||||
QLabel *label = new QLabel(QString("*%1 | %2").arg(account.lastNumbers, account.description), this);
|
||||
layout->addWidget(label);
|
||||
|
||||
setModal(true);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user