1
0
forked from BRT/arc
arc/android/black/GetProfileInfoScript.cpp
slava cae345cf61 Implement JS-based remote scripting framework for dynamic script updates:
- Add Proof of Concept (PoC) for executing JavaScript scripts fetched remotely.
- Integrate `Black::GetProfileInfoScript` logic into a JS version with fallback to C++ on errors.
- Extend `ScreenXmlParser` with additional JS-to-C++ bridge methods.
- Update `config.ini` for scripting configuration.
- Ensure scripts are securely fetched with SHA-256 validation and use an atomic caching mechanism.
- Add developer tools for debugging JS scripts and enable easier script updates without app re-deployment.
2026-05-13 15:42:10 +07:00

208 lines
7.9 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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

#include "GetProfileInfoScript.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QThread>
#include <QVariantMap>
#include "adb/AdbUtils.h"
#include "dao/MaterialDAO.h"
#include "dao/BankProfileDAO.h"
#include "dao/DeviceDAO.h"
#include "dump/TaskDumpRecorder.h"
#include "net/NetworkService.h"
#include "scripting/ScriptRuntime.h"
#include "scripting/ScriptingConfig.h"
namespace Black {
GetProfileInfoScript::GetProfileInfoScript(
QString deviceId,
QString appCode,
QObject *parent
) : CommonScript(parent),
m_deviceId(std::move(deviceId)),
m_appCode(std::move(appCode)) {
}
GetProfileInfoScript::~GetProfileInfoScript() = default;
void GetProfileInfoScript::doStart() {
// PoC удалённых JS-скриптов: пробуем JS-версию, при engine-ошибке делаем
// прозрачный fallback на C++ ниже. См. docs/remote_scripts.md.
if (Scripting::ScriptingConfig::isEnabled()
&& Scripting::ScriptingConfig::isJsEnabledFor("black/get_profile_info")) {
QString jsOut;
const QVariantMap params{
{"deviceId", m_deviceId},
{"appCode", m_appCode},
};
const auto status = Scripting::ScriptRuntime::run(
"black", "get_profile_info", params, &jsOut);
if (status == Scripting::ScriptRuntime::Result::Completed) {
m_error = jsOut;
emit finishedWithResult(m_error);
return;
}
qWarning() << "[Black::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);
if (device.id.isEmpty() || app.id == -1) {
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
qCritical() << m_error;
emit finishedWithResult(m_error);
return;
}
const TaskDumpMeta dumpMeta{};
TaskDumpRecorder::Scope dumpScope("black", "FETCH_PROFILE_INFO", m_deviceId, dumpMeta, &m_error);
// Читаем текущий экран
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
// Если не на домашнем экране — перезапускаем
if (!xmlScreenParser.isHomeScreen()) {
qDebug() << "[Black::GetProfileInfo] Not on home screen, restarting app...";
if (!runAppAndGoToHomeScreen(m_deviceId, app.package, app.pinCode,
device.screenWidth, device.screenHeight)) {
m_error = "Cannot reach home screen: " + device.name + " (" + m_deviceId + ") " + m_appCode;
qWarning() << m_error;
AdbUtils::tryToKillApplication(m_deviceId, app.package);
emit finishedWithResult(m_error);
return;
}
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
}
// 1. Тапаем по кнопке "¡Hola!" (аватар с именем) на домашнем экране
Node holaNode = xmlScreenParser.findHolaButton();
if (holaNode.isEmpty()) {
m_error = "Cannot find Hola button on home screen: " + m_deviceId;
qWarning() << m_error;
AdbUtils::tryToKillApplication(m_deviceId, app.package);
emit finishedWithResult(m_error);
return;
}
// Парсим имя и баланс с домашнего экрана
const QString fullName = xmlScreenParser.parseUserNameFromHomeScreen();
const double balance = xmlScreenParser.parseBalanceFromHomeScreen();
qDebug() << "[Black::GetProfileInfo] fullName from home:" << fullName << "balance:" << balance;
// Сохраняем баланс
BankProfileDAO::updateAmount(app.id, balance);
AdbUtils::makeTap(m_deviceId, holaNode.x(), holaNode.y());
QThread::msleep(2000);
// 2. Ждём боковое меню
bool sideMenuFound = false;
for (int attempt = 0; attempt < 5; ++attempt) {
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
if (xmlScreenParser.isSideMenu()) {
sideMenuFound = true;
break;
}
QThread::msleep(1000);
}
if (!sideMenuFound) {
m_error = "Side menu not detected: " + m_deviceId;
qWarning() << m_error;
AdbUtils::tryToKillApplication(m_deviceId, app.package);
emit finishedWithResult(m_error);
return;
}
// 3. Тапаем "Tu Información"
Node tuInfoNode = xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("Tu Información"));
if (tuInfoNode.isEmpty()) {
m_error = "Cannot find Tu Información button: " + m_deviceId;
qWarning() << m_error;
AdbUtils::tryToKillApplication(m_deviceId, app.package);
emit finishedWithResult(m_error);
return;
}
AdbUtils::makeTap(m_deviceId, tuInfoNode.x(), tuInfoNode.y());
QThread::msleep(2500);
// 4. Ждём страницу профиля "Tu información"
bool profileFound = false;
for (int attempt = 0; attempt < 5; ++attempt) {
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
if (xmlScreenParser.isProfilePage()) {
profileFound = true;
break;
}
QThread::msleep(1000);
}
if (!profileFound) {
m_error = "Profile page not detected: " + m_deviceId;
qWarning() << m_error;
AdbUtils::tryToKillApplication(m_deviceId, app.package);
emit finishedWithResult(m_error);
return;
}
// 5. Парсим данные профиля (первый экран: nombre, apellido, email)
const QString nombre = xmlScreenParser.parseProfileNombre();
const QString apellido = xmlScreenParser.parseProfileApellido();
const QString email = xmlScreenParser.parseProfileEmail();
// 6. Скроллим вниз чтобы увидеть телефон
const int x = device.screenWidth / 2;
const int fromY = device.screenHeight * 3 / 4;
const int toY = device.screenHeight / 4;
AdbUtils::makeSwipe(m_deviceId, x, fromY, x, toY);
QThread::msleep(1500);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
const QString phone = xmlScreenParser.parseProfilePhone();
// Собираем полное имя: "имя фамилия"
QString parsedFullName;
if (!nombre.isEmpty() && !apellido.isEmpty()) {
parsedFullName = nombre + " " + apellido;
} else if (!nombre.isEmpty()) {
parsedFullName = nombre;
} else {
parsedFullName = fullName; // fallback с домашнего экрана
}
qDebug() << "[Black::GetProfileInfo] nombre:" << nombre
<< "apellido:" << apellido
<< "email:" << email
<< "phone:" << phone
<< "fullName:" << parsedFullName;
// 7. Сохраняем в БД
if (!BankProfileDAO::updateProfileInfo(app.id, parsedFullName, phone, email)) {
qWarning() << "[Black::GetProfileInfo] Failed to save profile info to DB";
}
// 7. Создаём bank profile если нет
createBankProfileIfNeeded(m_deviceId, m_appCode);
// 8. Отправляем данные аккаунтов на сервер если нет material_id
const QList<MaterialInfo> allAccounts = MaterialDAO::getAccounts(device.id, m_appCode);
QList<QPair<MaterialInfo, Node>> accountsToPost;
for (const auto &acc : allAccounts) {
if (acc.materialId.isEmpty()) {
accountsToPost.append({acc, Node()});
}
}
if (!accountsToPost.isEmpty()) {
postAccountsData(accountsToPost);
}
emit finishedWithResult(m_error);
}
} // namespace Black