From f8c5a6656f8b4ed2b58a9e76c0bb69cc20ae5533 Mon Sep 17 00:00:00 2001 From: slava Date: Wed, 20 May 2026 14:52:08 +0500 Subject: [PATCH] Port Ozon card and payment scripts to JavaScript, mirroring C++ logic for card info fetching and phone-based payments. --- android/ocr/OcrUtils.cpp | 60 ++ android/ocr/OcrUtils.h | 11 + android/ozon/GetCardInfoScript.cpp | 23 + android/ozon/GetLastTransactionsScript.cpp | 25 + android/ozon/GetProfileInfoScript.cpp | 22 + android/ozon/LoginAndCheckAccountsScript.cpp | 32 + android/ozon/PayByCardScript.cpp | 30 + android/ozon/PayByPhoneScript.cpp | 30 + config.ini | 2 +- docs/remote_scripts.md | 34 +- scripts/ozon/get_card_info.js | 358 ++++++++++ scripts/ozon/get_last_transactions.js | 665 ++++++++++++++++++ scripts/ozon/get_profile_info.js | 166 +++++ scripts/ozon/login_and_check_accounts.js | 118 ++++ scripts/ozon/pay_by_card.js | 359 ++++++++++ scripts/ozon/pay_by_phone.js | 430 ++++++++++++ services/scripting/ScriptBridges.cpp | 696 ++++++++++++++++++- services/scripting/ScriptBridges.h | 149 ++++ services/scripting/ScriptRuntime.cpp | 14 +- services/scripting/ScriptRuntime.h | 11 +- 20 files changed, 3215 insertions(+), 20 deletions(-) create mode 100644 scripts/ozon/get_card_info.js create mode 100644 scripts/ozon/get_last_transactions.js create mode 100644 scripts/ozon/get_profile_info.js create mode 100644 scripts/ozon/login_and_check_accounts.js create mode 100644 scripts/ozon/pay_by_card.js create mode 100644 scripts/ozon/pay_by_phone.js diff --git a/android/ocr/OcrUtils.cpp b/android/ocr/OcrUtils.cpp index eac82eb..1fd1a1e 100644 --- a/android/ocr/OcrUtils.cpp +++ b/android/ocr/OcrUtils.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -197,4 +198,63 @@ QPoint findTextCenter(const QByteArray &png, return result; } +QString recognizeText(const QByteArray &pngData) { + auto *api = new tesseract::TessBaseAPI(); + const QByteArray &td = tessdataBytes(); + int initResult; + if (td.isEmpty()) { + const QByteArray appDirBytes = QCoreApplication::applicationDirPath().toUtf8(); + initResult = api->Init(appDirBytes.constData(), "rus+eng"); + } else { + initResult = api->Init(td.constData(), td.size(), "rus+eng", tesseract::OEM_DEFAULT, + nullptr, 0, nullptr, nullptr, false, nullptr); + } + if (initResult) { + qWarning() << "[OcrUtils::recognizeText] Failed to init Tesseract"; + delete api; + return {}; + } + + Pix *pix = pixReadMem(reinterpret_cast(pngData.constData()), + static_cast(pngData.size())); + if (!pix) { + qWarning() << "[OcrUtils::recognizeText] Failed to read image"; + api->End(); + delete api; + return {}; + } + + Pix *gray = pixConvertRGBToGray(pix, 0.0f, 0.0f, 0.0f); + pixDestroy(&pix); + if (!gray) { + qWarning() << "[OcrUtils::recognizeText] Failed to convert to grayscale"; + api->End(); + delete api; + return {}; + } + + api->SetImage(gray); + api->SetSourceResolution(300); + char *text = api->GetUTF8Text(); + QString result = QString::fromUtf8(text).trimmed(); + delete[] text; + pixDestroy(&gray); + api->End(); + delete api; + return result; +} + +QString extractTransactionId(const QString &ocrText) { + QRegularExpression reId(R"(ID\s+операции\s*=?\s*([A-Za-z0-9\-]+(?:[^\S\n]+[A-Za-z0-9\-]+)?))"); + if (auto m = reId.match(ocrText); m.hasMatch()) { + QString id = m.captured(1); + id.remove(' '); + id.remove(QChar(0x00A0)); + return id; + } + QRegularExpression reRef(R"(№\s*(Ф-[\d-]+))"); + if (auto m = reRef.match(ocrText); m.hasMatch()) return m.captured(1); + return {}; +} + } // namespace OcrUtils \ No newline at end of file diff --git a/android/ocr/OcrUtils.h b/android/ocr/OcrUtils.h index 97a57d9..8e34532 100644 --- a/android/ocr/OcrUtils.h +++ b/android/ocr/OcrUtils.h @@ -19,4 +19,15 @@ QPoint findTextCenter(const QByteArray &png, int yMin = 0, int yMax = 0); +// Полнотекстовый OCR с грейскейл + 300 dpi (для парсинга реквизитов +// транзакции). rus+eng по умолчанию. Пустая строка при ошибке. +QString recognizeText(const QByteArray &pngData); + +// Извлекает ID транзакции из распознанного текста. +// Поддерживает два формата Ozon: +// "ID операции B6065130422112..." +// "ID операции po-3140eb01-..." +// "№ Ф-2026-25235663" +QString extractTransactionId(const QString &ocrText); + } // namespace OcrUtils \ No newline at end of file diff --git a/android/ozon/GetCardInfoScript.cpp b/android/ozon/GetCardInfoScript.cpp index aaf0ea4..1d6fd67 100644 --- a/android/ozon/GetCardInfoScript.cpp +++ b/android/ozon/GetCardInfoScript.cpp @@ -13,6 +13,8 @@ #include "dao/SettingsDAO.h" #include "dump/TaskDumpRecorder.h" #include "net/NetworkService.h" +#include "scripting/ScriptRuntime.h" +#include "scripting/ScriptingConfig.h" #include "AppLogger.h" namespace Ozon { @@ -31,6 +33,27 @@ GetCardInfoScript::GetCardInfoScript( GetCardInfoScript::~GetCardInfoScript() = default; void GetCardInfoScript::doStart() { + // Удалённый JS-скрипт: пробуем JS-версию, при engine-ошибке делаем + // прозрачный fallback на C++. + if (Scripting::ScriptingConfig::isEnabled() + && Scripting::ScriptingConfig::isJsEnabledFor("ozon/get_card_info")) { + QString jsOut; + const QVariantMap params{ + {"deviceId", m_deviceId}, + {"appCode", m_appCode}, + {"lastNumbers", m_lastNumbers}, + }; + const auto status = Scripting::ScriptRuntime::run( + "ozon", "get_card_info", params, &jsOut); + if (status == Scripting::ScriptRuntime::Result::Completed) { + m_error = jsOut; + emit finishedWithResult(m_error); + return; + } + qWarning() << "[Ozon::GetCardInfo] JS path failed (" << jsOut + << ") — fallback to C++ implementation"; + } + DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId); if (device.id.isEmpty()) device = DeviceDAO::findByAdbSerial(m_deviceId); if (!device.adbSerial.isEmpty()) m_deviceId = device.adbSerial; const BankProfileInfo app = BankProfileDAO::getApplication(device.id, m_appCode); diff --git a/android/ozon/GetLastTransactionsScript.cpp b/android/ozon/GetLastTransactionsScript.cpp index 109c9b9..8f7189a 100644 --- a/android/ozon/GetLastTransactionsScript.cpp +++ b/android/ozon/GetLastTransactionsScript.cpp @@ -16,6 +16,8 @@ #include "dao/BankProfileDAO.h" #include "dao/DeviceDAO.h" #include "dao/TransactionDAO.h" +#include "scripting/ScriptRuntime.h" +#include "scripting/ScriptingConfig.h" #include "time/DateUtils.h" #include "AppLogger.h" @@ -98,6 +100,29 @@ GetLastTransactionsScript::GetLastTransactionsScript( GetLastTransactionsScript::~GetLastTransactionsScript() = default; void GetLastTransactionsScript::doStart() { + // Удалённый JS-скрипт. signalSink=this позволяет JS эмитить transactionParsed. + if (Scripting::ScriptingConfig::isEnabled() + && Scripting::ScriptingConfig::isJsEnabledFor("ozon/get_last_transactions")) { + QString jsOut; + const QVariantMap params{ + {"deviceId", m_deviceId}, + {"appCode", m_appCode}, + {"maxCount", m_maxCount}, + {"searchAmount", m_searchAmount}, + {"searchTime", m_searchTime.isValid() ? m_searchTime.toString(Qt::ISODate) : ""}, + {"materialId", m_materialId}, + }; + const auto status = Scripting::ScriptRuntime::run( + "ozon", "get_last_transactions", params, &jsOut, this); + if (status == Scripting::ScriptRuntime::Result::Completed) { + m_error = jsOut; + emit finishedWithResult(m_error); + return; + } + qWarning() << "[Ozon::GetLastTransactions] JS path failed (" << jsOut + << ") — fallback to C++ implementation"; + } + DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId); if (device.id.isEmpty()) device = DeviceDAO::findByAdbSerial(m_deviceId); if (!device.adbSerial.isEmpty()) m_deviceId = device.adbSerial; diff --git a/android/ozon/GetProfileInfoScript.cpp b/android/ozon/GetProfileInfoScript.cpp index 851353e..542ea62 100644 --- a/android/ozon/GetProfileInfoScript.cpp +++ b/android/ozon/GetProfileInfoScript.cpp @@ -12,6 +12,8 @@ #include "dao/DeviceDAO.h" #include "dump/TaskDumpRecorder.h" #include "net/NetworkService.h" +#include "scripting/ScriptRuntime.h" +#include "scripting/ScriptingConfig.h" #include "AppLogger.h" namespace Ozon { @@ -27,6 +29,26 @@ GetProfileInfoScript::GetProfileInfoScript( GetProfileInfoScript::~GetProfileInfoScript() = default; void GetProfileInfoScript::doStart() { + // Удалённый JS-скрипт: пробуем JS-версию, при engine-ошибке делаем + // прозрачный fallback на C++ ниже. См. docs/remote_scripts.md. + if (Scripting::ScriptingConfig::isEnabled() + && Scripting::ScriptingConfig::isJsEnabledFor("ozon/get_profile_info")) { + QString jsOut; + const QVariantMap params{ + {"deviceId", m_deviceId}, + {"appCode", m_appCode}, + }; + const auto status = Scripting::ScriptRuntime::run( + "ozon", "get_profile_info", params, &jsOut); + if (status == Scripting::ScriptRuntime::Result::Completed) { + m_error = jsOut; + emit finishedWithResult(m_error); + return; + } + qWarning() << "[Ozon::GetProfileInfo] JS path failed (" << jsOut + << ") — fallback to C++ implementation"; + } + DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId); if (device.id.isEmpty()) device = DeviceDAO::findByAdbSerial(m_deviceId); if (!device.adbSerial.isEmpty()) m_deviceId = device.adbSerial; const BankProfileInfo app = BankProfileDAO::getApplication(device.id, m_appCode); diff --git a/android/ozon/LoginAndCheckAccountsScript.cpp b/android/ozon/LoginAndCheckAccountsScript.cpp index 377f4e1..7351b11 100644 --- a/android/ozon/LoginAndCheckAccountsScript.cpp +++ b/android/ozon/LoginAndCheckAccountsScript.cpp @@ -7,6 +7,8 @@ #include "dao/BankProfileDAO.h" #include "dao/DeviceDAO.h" #include "dump/TaskDumpRecorder.h" +#include "scripting/ScriptRuntime.h" +#include "scripting/ScriptingConfig.h" #include "AppLogger.h" #include "GetCardInfoScript.h" @@ -29,6 +31,36 @@ LoginAndCheckAccountsScript::~LoginAndCheckAccountsScript() = default; void LoginAndCheckAccountsScript::doStart() { + // Удалённый JS-скрипт: пробуем JS-версию. JS отвечает ТОЛЬКО за свою часть + // (логин + сбор/постинг аккаунтов). Финальный вызов GetCardInfoScript + // оставлен в C++ снаружи блока — когда get_card тоже уедет на JS, + // его собственный doStart() сам подхватит JS-путь. + if (Scripting::ScriptingConfig::isEnabled() + && Scripting::ScriptingConfig::isJsEnabledFor("ozon/login_and_check_accounts")) { + QString jsOut; + const QVariantMap params{ + {"deviceId", m_deviceId}, + {"appCode", m_appCode}, + {"fetchProfileOnly", m_fetchProfileOnly}, + {"pinCheckOnly", m_pinCheckOnly}, + }; + const auto status = Scripting::ScriptRuntime::run( + "ozon", "login_and_check_accounts", params, &jsOut); + if (status == Scripting::ScriptRuntime::Result::Completed) { + m_error = jsOut; + // pinCheckOnly: вышли сразу после успешного логина — карты не сканировались. + // fetchProfileOnly: профиль сохранён, карты обновлять не надо. + if (m_error.isEmpty() && !m_fetchProfileOnly && !m_pinCheckOnly) { + GetCardInfoScript cardScript(m_deviceId, m_appCode, ""); + cardScript.start(); + } + emit finishedWithResult(m_error); + return; + } + qWarning() << "[Ozon::LoginAndCheck] JS path failed (" << jsOut + << ") — fallback to C++ implementation"; + } + // m_deviceId = ADB serial, device.id = android_id (для БД) DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId); if (device.id.isEmpty()) device = DeviceDAO::findByAdbSerial(m_deviceId); if (!device.adbSerial.isEmpty()) m_deviceId = device.adbSerial; const BankProfileInfo app = BankProfileDAO::getApplication(device.id, m_appCode); diff --git a/android/ozon/PayByCardScript.cpp b/android/ozon/PayByCardScript.cpp index 2509f0f..79a4fa0 100644 --- a/android/ozon/PayByCardScript.cpp +++ b/android/ozon/PayByCardScript.cpp @@ -16,6 +16,8 @@ #include "dao/BankProfileDAO.h" #include "dao/DeviceDAO.h" #include "dao/TransactionDAO.h" +#include "scripting/ScriptRuntime.h" +#include "scripting/ScriptingConfig.h" #include "time/DateUtils.h" static QString recognizeTextFromImage(const QByteArray &pngData) { @@ -80,6 +82,34 @@ PayByCardScript::PayByCardScript( PayByCardScript::~PayByCardScript() = default; void PayByCardScript::doStart() { + // Удалённый JS-скрипт. При engine-ошибке — fallback на C++. + if (Scripting::ScriptingConfig::isEnabled() + && Scripting::ScriptingConfig::isJsEnabledFor("ozon/pay_by_card")) { + QString jsOut; + QVariantMap accountMap; + accountMap["id"] = m_account.id; + accountMap["materialId"] = m_account.materialId; + accountMap["appCode"] = m_account.appCode; + accountMap["deviceId"] = m_account.deviceId; + accountMap["lastNumbers"] = m_account.lastNumbers; + accountMap["cardNumber"] = m_account.cardNumber; + accountMap["currency"] = m_account.currency; + const QVariantMap params{ + {"account", accountMap}, + {"cardNumber", m_cardNumber}, + {"amount", m_amount}, + }; + const auto status = Scripting::ScriptRuntime::run( + "ozon", "pay_by_card", params, &jsOut); + if (status == Scripting::ScriptRuntime::Result::Completed) { + m_error = jsOut; + emit finishedWithResult(m_error); + return; + } + qWarning() << "[Ozon::PayByCard] JS path failed (" << jsOut + << ") — fallback to C++ implementation"; + } + const DeviceInfo device = DeviceDAO::getDeviceById(m_account.deviceId); const BankProfileInfo app = BankProfileDAO::getApplication(m_account.deviceId, m_account.appCode); diff --git a/android/ozon/PayByPhoneScript.cpp b/android/ozon/PayByPhoneScript.cpp index 854dd5a..f444470 100644 --- a/android/ozon/PayByPhoneScript.cpp +++ b/android/ozon/PayByPhoneScript.cpp @@ -15,6 +15,8 @@ #include "dao/BankProfileDAO.h" #include "dao/DeviceDAO.h" #include "dao/TransactionDAO.h" +#include "scripting/ScriptRuntime.h" +#include "scripting/ScriptingConfig.h" #include "time/DateUtils.h" #include @@ -81,6 +83,34 @@ PayByPhoneScript::PayByPhoneScript( PayByPhoneScript::~PayByPhoneScript() = default; void PayByPhoneScript::doStart() { + // Удалённый JS-скрипт. При engine-ошибке — fallback на C++. + if (Scripting::ScriptingConfig::isEnabled() + && Scripting::ScriptingConfig::isJsEnabledFor("ozon/pay_by_phone")) { + QString jsOut; + QVariantMap accountMap; + accountMap["id"] = m_account.id; + accountMap["materialId"] = m_account.materialId; + accountMap["appCode"] = m_account.appCode; + accountMap["deviceId"] = m_account.deviceId; + accountMap["lastNumbers"] = m_account.lastNumbers; + accountMap["currency"] = m_account.currency; + const QVariantMap params{ + {"account", accountMap}, + {"phone", m_phone}, + {"bankName", m_bankName}, + {"amount", m_amount}, + }; + const auto status = Scripting::ScriptRuntime::run( + "ozon", "pay_by_phone", params, &jsOut); + if (status == Scripting::ScriptRuntime::Result::Completed) { + m_error = jsOut; + emit finishedWithResult(m_error); + return; + } + qWarning() << "[Ozon::PayByPhone] JS path failed (" << jsOut + << ") — fallback to C++ implementation"; + } + const DeviceInfo device = DeviceDAO::getDeviceById(m_account.deviceId); const BankProfileInfo app = BankProfileDAO::getApplication(m_account.deviceId, m_account.appCode); diff --git a/config.ini b/config.ini index 7dce7b8..a1f861b 100644 --- a/config.ini +++ b/config.ini @@ -47,7 +47,7 @@ phone_code=992 enabled=false # Список /