228 lines
9.8 KiB
C++
228 lines
9.8 KiB
C++
#include "GetProfileInfoScript.h"
|
||
|
||
#include <QJsonDocument>
|
||
#include <QJsonObject>
|
||
#include <QMap>
|
||
#include <QtGlobal>
|
||
#include <QThread>
|
||
|
||
#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"
|
||
#include "AppLogger.h"
|
||
|
||
namespace Ozon {
|
||
|
||
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() {
|
||
// Удалённый 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);
|
||
|
||
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("ozon", "FETCH_PROFILE_INFO", m_deviceId, dumpMeta, &m_error);
|
||
|
||
// Читаем текущий экран
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
|
||
// Закрываем рекламный bottom sheet, если открыт (home_ad_1)
|
||
if (Node adNode = xmlScreenParser.tryToFindAdBanner(); !adNode.isEmpty()) {
|
||
qDebug() << "[GetProfileInfo] Ad banner detected, closing...";
|
||
AdbUtils::makeTap(m_deviceId, adNode.x(), adNode.y());
|
||
QThread::msleep(1000);
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
}
|
||
|
||
// Свежий дамп перед проверкой домашнего экрана
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
|
||
// Если мы не на домашнем экране — перезапускаем приложение и вводим пин-код
|
||
if (!xmlScreenParser.isHomeScreen()) {
|
||
qDebug() << "[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;
|
||
}
|
||
// После runAppAndGoToHomeScreen xml уже обновлён внутри метода,
|
||
// но делаем свежий дамп, чтобы убедиться
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
}
|
||
|
||
// Закрываем баннеры на главной, если есть
|
||
tryToCloseAllBannersOnHomePage(m_deviceId, device.screenWidth, device.screenHeight);
|
||
QThread::msleep(500);
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
|
||
// Парсим баланс с домашнего экрана
|
||
const QList<MaterialInfo> parsedCards = xmlScreenParser.parseCardsOnHomeScreen();
|
||
double parsedBalance = parsedCards.isEmpty() ? 0.0 : parsedCards[0].amount;
|
||
qDebug() << "[GetProfileInfo] parseCardsOnHomeScreen returned"
|
||
<< parsedCards.size() << "cards, balance:" << parsedBalance;
|
||
// Fallback: если парсинг не нашёл баланс, берём из materials
|
||
if (parsedBalance <= 0.0) {
|
||
const QList<MaterialInfo> accounts = MaterialDAO::getAccounts(device.id, m_appCode);
|
||
for (const auto &acc : accounts) {
|
||
if (acc.amount > 0.0) { parsedBalance = acc.amount; break; }
|
||
}
|
||
qDebug() << "[GetProfileInfo] Fallback balance from materials:" << parsedBalance;
|
||
}
|
||
if (parsedBalance > 0.0) {
|
||
BankProfileDAO::updateAmount(app.id, parsedBalance);
|
||
}
|
||
|
||
// Находим имя пользователя в верхней части домашнего экрана и тапаем по нему
|
||
Node userNameNode = xmlScreenParser.findUserNameOnHomeScreen(device.screenHeight);
|
||
if (userNameNode.isEmpty()) {
|
||
m_error = "Cannot find user name on home screen: " + m_deviceId;
|
||
qWarning() << m_error;
|
||
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
||
emit finishedWithResult(m_error);
|
||
return;
|
||
}
|
||
|
||
qDebug() << "[GetProfileInfo] Tapping on user name:" << userNameNode.text
|
||
<< "at" << userNameNode.x() << userNameNode.y();
|
||
AdbUtils::makeTap(m_deviceId, userNameNode.x(), userNameNode.y());
|
||
|
||
// Ждём загрузки страницы профиля
|
||
QThread::msleep(2500);
|
||
|
||
// Читаем XML профиля и проверяем что мы действительно там
|
||
for (int attempt = 0; attempt < 5; ++attempt) {
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
closeGooglePlayBannerIfVisible(m_deviceId, device.screenWidth, device.screenHeight);
|
||
if (xmlScreenParser.isProfileScreen()) {
|
||
break;
|
||
}
|
||
QThread::msleep(1000);
|
||
}
|
||
|
||
if (!xmlScreenParser.isProfileScreen()) {
|
||
m_error = "Profile screen not detected after tap: " + m_deviceId;
|
||
qWarning() << m_error;
|
||
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
||
emit finishedWithResult(m_error);
|
||
return;
|
||
}
|
||
|
||
// Извлекаем полное имя и телефон
|
||
const QString fullName = xmlScreenParser.parseProfileFullName();
|
||
QString phone = xmlScreenParser.parseProfilePhone();
|
||
phone.remove(' ');
|
||
phone.remove(QChar(0x00A0));
|
||
|
||
qDebug() << "[GetProfileInfo] fullName:" << fullName << "phone:" << phone;
|
||
|
||
if (!BankProfileDAO::updateProfileInfo(app.id, fullName, phone)) {
|
||
m_error = "Failed to save profile info to DB: " + m_deviceId;
|
||
qWarning() << m_error;
|
||
}
|
||
|
||
// Отправляем bank_profile на сервер — используем свежепарсенные данные
|
||
const BankProfileInfo freshApp = BankProfileDAO::getApplication(device.id, m_appCode);
|
||
|
||
const QMap<QString, int> currencyCodes = {
|
||
{"RUB", 643}, {"USD", 840}, {"KRW", 410}, {"AZN", 944}, {"ARS", 32}, {"TJS", 972}
|
||
};
|
||
const int currencyCode = currencyCodes.value(freshApp.currency.toUpper(), 643);
|
||
|
||
const QStringList parts = fullName.split(' ', Qt::SkipEmptyParts);
|
||
QJsonObject profileBody;
|
||
profileBody["first_name"] = parts.value(0);
|
||
profileBody["middle_name"] = parts.size() >= 3 ? parts.value(1) : "";
|
||
profileBody["last_name"] = parts.size() >= 3 ? parts.value(2) : parts.value(1);
|
||
profileBody["bank_name"] = m_appCode;
|
||
profileBody["phone_number"] = phone;
|
||
profileBody["balance"] = QString::number(qRound64(parsedBalance * 100));
|
||
profileBody["state"] = (freshApp.status == "active") ? "enabled" : "disabled";
|
||
profileBody["device_id"] = device.apiId;
|
||
profileBody["currency"] = currencyCode;
|
||
|
||
NetworkService ns;
|
||
ns.setDeviceId(m_deviceId);
|
||
ns.setPayload(QJsonDocument(profileBody).toJson());
|
||
|
||
if (freshApp.bankProfileId.isEmpty()) {
|
||
ns.addExtra("app_id", freshApp.id);
|
||
ns.postBankProfile();
|
||
const BankProfileInfo afterPost = BankProfileDAO::getApplication(device.id, m_appCode);
|
||
if (afterPost.bankProfileId.isEmpty()) {
|
||
const QString err = "postBankProfile failed for " + m_deviceId;
|
||
qWarning() << "[GetProfileInfo]" << err;
|
||
AppLogger::log("ozon/GetProfileInfo", m_deviceId, err,
|
||
AdbUtils::captureScreenshotBytes(m_deviceId),
|
||
"FETCH_PROFILE");
|
||
} else {
|
||
qDebug() << "[GetProfileInfo] postBankProfile done";
|
||
}
|
||
} else {
|
||
ns.addExtra("bank_profile_id", freshApp.bankProfileId);
|
||
ns.patchBankProfile();
|
||
qDebug() << "[GetProfileInfo] patchBankProfile done";
|
||
}
|
||
|
||
// Отправляем данные аккаунтов на сервер если нет 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);
|
||
}
|
||
|
||
// Возвращаемся на домашний экран
|
||
AdbUtils::goBack(m_deviceId);
|
||
QThread::msleep(1000);
|
||
|
||
emit finishedWithResult(m_error);
|
||
}
|
||
|
||
} // namespace Ozon
|