#include "ScriptBridges.h" #include #include #include #include #include #include #include #include #include #include "adb/AdbUtils.h" #include "black/ScreenXmlParser.h" #include "android/xml/Node.h" #include "db/BankProfileInfo.h" #include "db/DeviceInfo.h" #include "db/MaterialInfo.h" #include "dao/BankProfileDAO.h" #include "dao/DeviceDAO.h" #include "dao/MaterialDAO.h" #include "dump/TaskDumpRecorder.h" #include "net/NetworkService.h" namespace Scripting { QVariantMap nodeToVariantMap(const Node &node) { Node copy = node; // isEmpty() не const, нельзя дернуть на const-ссылке QVariantMap m; m["x1"] = copy.x1(); m["y1"] = copy.y1(); m["x2"] = copy.x2(); m["y2"] = copy.y2(); m["x"] = copy.x(); m["y"] = copy.y(); m["width"] = copy.width(); m["height"] = copy.height(); m["text"] = copy.text; m["contentDesc"] = copy.contentDesc; m["resourceId"] = copy.resourceId; m["className"] = copy.className; m["hint"] = copy.hint; m["clickable"] = copy.clickable; m["focusable"] = copy.focusable; m["password"] = copy.password; m["isEmpty"] = copy.isEmpty(); return m; } // ─── LogBridge ────────────────────────────────────────────────────────────── void LogBridge::debug(const QString &message) { qDebug().noquote() << "[js:" + m_tag + "]" << message; } void LogBridge::info (const QString &message) { qInfo().noquote() << "[js:" + m_tag + "]" << message; } void LogBridge::warn (const QString &message) { qWarning().noquote() << "[js:" + m_tag + "]" << message; } void LogBridge::error(const QString &message) { qCritical().noquote() << "[js:" + m_tag + "]" << message; } // ─── TimerBridge ──────────────────────────────────────────────────────────── void TimerBridge::sleep(int ms) { if (ms <= 0) return; // Дробим длинный sleep на 100мс шаги, чтобы можно было прервать поток. const int step = 100; while (ms > 0) { if (QThread *t = QThread::currentThread(); t && t->isInterruptionRequested()) return; const int chunk = ms > step ? step : ms; QThread::msleep(static_cast(chunk)); ms -= chunk; } } bool TimerBridge::isInterrupted() const { QThread *t = QThread::currentThread(); return t && t->isInterruptionRequested(); } // ─── AdbBridge ────────────────────────────────────────────────────────────── QString AdbBridge::getScreenDumpAsXml(const QString &deviceId, int idx) { return AdbUtils::getScreenDumpAsXml(deviceId, idx); } QString AdbBridge::getRawScreenDumpAsXml(const QString &deviceId) { return AdbUtils::getRawScreenDumpAsXml(deviceId); } bool AdbBridge::makeTap(const QString &deviceId, int x, int y) { return AdbUtils::makeTap(deviceId, x, y); } bool AdbBridge::makeSwipe(const QString &deviceId, int x1, int y1, int x2, int y2) { return AdbUtils::makeSwipe(deviceId, x1, y1, x2, y2); } bool AdbBridge::makeSwipeWithDuration(const QString &deviceId, int x1, int y1, int x2, int y2, int durationMs) { return AdbUtils::makeSwipe(deviceId, x1, y1, x2, y2, durationMs); } bool AdbBridge::tryToStartApplication(const QString &deviceId, const QString &packageName) { return AdbUtils::tryToStartApplication(deviceId, packageName); } bool AdbBridge::tryToKillApplication(const QString &deviceId, const QString &packageName) { return AdbUtils::tryToKillApplication(deviceId, packageName); } QString AdbBridge::tryToGetRunningAppPid(const QString &deviceId, const QString &packageName) { return AdbUtils::tryToGetRunningAppPid(deviceId, packageName); } bool AdbBridge::takeScreenshot(const QString &deviceId, const QString &name) { return AdbUtils::takeScreenshot(deviceId, name); } bool AdbBridge::inputText(const QString &deviceId, const QString &text) { return AdbUtils::inputText(deviceId, text); } bool AdbBridge::inputAdbIMEText(const QString &deviceId, const QString &text) { return AdbUtils::inputAdbIMEText(deviceId, text); } bool AdbBridge::enableAdbIMEKeyboard(const QString &deviceId) { return AdbUtils::enableAdbIMEKeyboard(deviceId); } bool AdbBridge::disableAdbIMEKeyboard(const QString &deviceId) { return AdbUtils::disableAdbIMEKeyboard(deviceId); } bool AdbBridge::hideKeyboard(const QString &deviceId) { return AdbUtils::hideKeyboard(deviceId); } bool AdbBridge::goBack(const QString &deviceId) { return AdbUtils::goBack(deviceId); } void AdbBridge::unlockScreen(const QString &deviceId, int screenWidth, int screenHeight) { AdbUtils::unlockScreen(deviceId, screenWidth, screenHeight); } QString AdbBridge::getDeviceTimezone(const QString &deviceId) { return AdbUtils::getDeviceTimezone(deviceId); } QString AdbBridge::captureScreenshotBase64(const QString &deviceId) { const QByteArray bytes = AdbUtils::captureScreenshotBytes(deviceId); return QString::fromLatin1(bytes.toBase64()); } // ─── DaoBridge ────────────────────────────────────────────────────────────── static QVariantMap deviceToMap(const DeviceInfo &d) { QVariantMap m; if (d.id.isEmpty()) return m; // пустая мапа = null m["id"] = d.id; m["adbSerial"] = d.adbSerial; m["name"] = d.name; m["screenWidth"] = d.screenWidth; m["screenHeight"] = d.screenHeight; m["apiId"] = d.apiId; return m; } static QVariantMap bankProfileToMap(const BankProfileInfo &p) { QVariantMap m; if (p.id == -1) return m; m["id"] = p.id; m["appCode"] = p.code; // BankProfileInfo.code = код банка в config.ini ("black", "ozon"...) m["name"] = p.name; m["package"] = p.package; m["pinCode"] = p.pinCode; m["status"] = p.status; m["amount"] = p.amount; m["fullName"] = p.fullName; m["phone"] = p.phone; m["email"] = p.email; m["currency"] = p.currency; m["bankProfileId"] = p.bankProfileId; return m; } static QVariantMap materialToMap(const MaterialInfo &a) { QVariantMap m; if (a.id == -1) return m; m["id"] = a.id; m["materialId"] = a.materialId; m["appCode"] = a.appCode; m["deviceId"] = a.deviceId; m["lastNumbers"] = a.lastNumbers; m["amount"] = a.amount; m["currency"] = a.currency; m["description"] = a.description; m["status"] = materialStatusToString(a.status); return m; } QVariantMap DaoBridge::deviceGetById(const QString &id) { return deviceToMap(DeviceDAO::getDeviceById(id)); } QVariantMap DaoBridge::deviceFindByAdbSerial(const QString &serial) { return deviceToMap(DeviceDAO::findByAdbSerial(serial)); } QVariantMap DaoBridge::bankProfileGet(const QString &deviceId, const QString &appCode) { return bankProfileToMap(BankProfileDAO::getApplication(deviceId, appCode)); } bool DaoBridge::bankProfileUpdateAmount(int profileId, double amount) { return BankProfileDAO::updateAmount(profileId, amount); } bool DaoBridge::bankProfileUpdateProfileInfo(int profileId, const QString &fullName, const QString &phone, const QString &email) { return BankProfileDAO::updateProfileInfo(profileId, fullName, phone, email); } bool DaoBridge::bankProfileUpdateComment(int profileId, const QString &comment) { return BankProfileDAO::updateComment(profileId, comment); } QVariantList DaoBridge::materialGetAccounts(const QString &deviceId, const QString &appCode) { QVariantList list; for (const MaterialInfo &acc : MaterialDAO::getAccounts(deviceId, appCode)) { list.append(materialToMap(acc)); } return list; } // ─── DumpBridge ───────────────────────────────────────────────────────────── void DumpBridge::beginScope(const QString &bank, const QString &taskKind, const QString &adbDeviceId, const QVariantMap &meta) { if (m_scope) { qWarning() << "[Scripting] DumpBridge: beginScope called twice, ignoring"; return; } TaskDumpMeta tm; tm.materialId = meta.value("materialId").toString(); tm.amount = meta.value("amount").toDouble(); tm.phone = meta.value("phone").toString(); tm.card = meta.value("card").toString(); m_errorPtr = new QString(); m_scope = new TaskDumpRecorder::Scope(bank, taskKind, adbDeviceId, tm, m_errorPtr); } void DumpBridge::endScope(const QString &errorMessage) { if (!m_scope) return; if (m_errorPtr) *m_errorPtr = errorMessage; delete static_cast(m_scope); m_scope = nullptr; delete m_errorPtr; m_errorPtr = nullptr; } // ─── BlackParserBridge ────────────────────────────────────────────────────── BlackParserBridge::BlackParserBridge(QObject *parent) : QObject(parent), m_parser(new Black::ScreenXmlParser(this)) {} BlackParserBridge::~BlackParserBridge() = default; void BlackParserBridge::parseAndSaveXml(const QString &xml) { m_parser->parseAndSaveXml(xml); } bool BlackParserBridge::isHomeScreen() { return m_parser->isHomeScreen(); } bool BlackParserBridge::isSideMenu() { return m_parser->isSideMenu(); } bool BlackParserBridge::isPinCodeScreen() { return m_parser->isPinCodeScreen(); } bool BlackParserBridge::isProfilePage() { return m_parser->isProfilePage(); } bool BlackParserBridge::isCVUScreen() { return m_parser->isCVUScreen(); } bool BlackParserBridge::isTransactionLoadingScreen() { return m_parser->isTransactionLoadingScreen(); } bool BlackParserBridge::isTransactionListScreen() { return m_parser->isTransactionListScreen(); } bool BlackParserBridge::isPayInputCardNumberScreen() { return m_parser->isPayInputCardNumberScreen(); } bool BlackParserBridge::isPayTransferConfirmScreen() { return m_parser->isPayTransferConfirmScreen(); } bool BlackParserBridge::isPayInputSumScreen() { return m_parser->isPayInputSumScreen(); } bool BlackParserBridge::isPayConfirmScreen() { return m_parser->isPayConfirmScreen(); } bool BlackParserBridge::isPaySuccessScreen() { return m_parser->isPaySuccessScreen(); } QVariantMap BlackParserBridge::findHolaButton() { return nodeToVariantMap(m_parser->findHolaButton()); } QVariantMap BlackParserBridge::findTextNode(const QString &text) { return nodeToVariantMap(m_parser->findTextNode(text)); } QVariantMap BlackParserBridge::findNodeByContentDesc(const QString &desc) { return nodeToVariantMap(m_parser->findNodeByContentDesc(desc)); } QVariantMap BlackParserBridge::findNodeByContentDescContains(const QString &desc) { return nodeToVariantMap(m_parser->findNodeByContentDesc(desc, true)); } QVariantMap BlackParserBridge::findButtonNode(const QString &text, bool contains) { return nodeToVariantMap(m_parser->findButtonNode(text, contains)); } QVariantMap BlackParserBridge::findFirstEditText() { return nodeToVariantMap(m_parser->findFirstEditText()); } QVariantMap BlackParserBridge::findPinCodeEditText() { return nodeToVariantMap(m_parser->findPinCodeEditText()); } QVariantMap BlackParserBridge::findVerMasAfterTuActividad() { return nodeToVariantMap(m_parser->findVerMasAfterTuActividad()); } QVariantMap BlackParserBridge::findEditTextByHint(const QString &hint) { return nodeToVariantMap(m_parser->findEditTextByHint(hint)); } QString BlackParserBridge::parseUserNameFromHomeScreen() { return m_parser->parseUserNameFromHomeScreen(); } double BlackParserBridge::parseBalanceFromHomeScreen() { return m_parser->parseBalanceFromHomeScreen(); } QString BlackParserBridge::parseProfileNombre() { return m_parser->parseProfileNombre(); } QString BlackParserBridge::parseProfileApellido() { return m_parser->parseProfileApellido(); } QString BlackParserBridge::parseProfileEmail() { return m_parser->parseProfileEmail(); } QString BlackParserBridge::parseProfilePhone() { return m_parser->parseProfilePhone(); } QString BlackParserBridge::parseCVUNumber() { return m_parser->parseCVUNumber(); } QString BlackParserBridge::parseRecipientNameFromConfirm(){ return m_parser->parseRecipientNameFromConfirm(); } QString BlackParserBridge::parsePaySuccessDateTime() { return m_parser->parsePaySuccessDateTime(); } QString BlackParserBridge::parsePaySuccessCoelsaId() { return m_parser->parsePaySuccessCoelsaId(); } // ─── BlackHelpersBridge ───────────────────────────────────────────────────── // Локальная копия логики Black::CommonScript::runAppAndGoToHomeScreen — нужна // чтобы JS мог вызвать готовый высокоуровневый flow, не переписывая login заново. bool BlackHelpersBridge::runAppAndGoToHomeScreen(const QString &deviceId, const QString &packageName, const QString &pinCode, int width, int height) { Black::ScreenXmlParser *parser = m_parserBridge->parser(); AdbUtils::unlockScreen(deviceId, width, height); if (const QString pid = AdbUtils::tryToGetRunningAppPid(deviceId, packageName); !pid.isEmpty()) { qDebug() << "[js:black/helpers] 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() << "[js:black/helpers] Process has not started"; return false; } QThread::msleep(4500); int counter = 0; bool pinInjected = false; int xmlCounter = 0; while (true) { if (QThread *t = QThread::currentThread(); t && t->isInterruptionRequested()) return false; counter++; if (counter > 10) { AdbUtils::takeScreenshot(deviceId, "too_many_attempts"); return false; } parser->parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++xmlCounter)); if (parser->isHomeScreen()) return true; if (!pinInjected && parser->isPinCodeScreen()) { // Inline-копия inputPinCode (тоже из CommonScript) Node editText = parser->findPinCodeEditText(); if (!editText.isEmpty()) { AdbUtils::makeTap(deviceId, editText.x(), editText.y()); QThread::msleep(500); AdbUtils::inputText(deviceId, pinCode); QThread::msleep(500); } else { qWarning() << "[js:black/helpers] PIN EditText not found"; } pinInjected = true; QThread::msleep(3000); continue; } QThread::msleep(1000); } } void BlackHelpersBridge::postAccountsData(const QVariantList &accounts) { if (accounts.isEmpty()) return; auto *thread = new QThread; auto *worker = new NetworkService; QJsonArray list; QString deviceId; for (const QVariant &v : accounts) { const QVariantMap acc = v.toMap(); QJsonObject obj; obj["appName"] = acc.value("appCode").toString(); obj["lastNumbers"] = acc.value("lastNumbers").toString(); obj["amount"] = acc.value("amount").toDouble(); obj["description"] = acc.value("description").toString(); obj["currency"] = acc.value("currency").toString(); obj["status"] = acc.value("status").toString(); list.append(obj); if (deviceId.isEmpty()) deviceId = acc.value("deviceId").toString(); } worker->setDeviceId(deviceId); worker->setPayload(QJsonDocument(list).toJson()); worker->moveToThread(thread); QObject::connect(thread, &QThread::started, worker, &NetworkService::postAccountsData); QObject::connect(worker, &NetworkService::finished, thread, &QThread::quit); QObject::connect(worker, &NetworkService::finished, worker, &QObject::deleteLater); QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater); thread->start(); } void BlackHelpersBridge::createBankProfileIfNeeded(const QString &deviceId, const QString &appCode, double parsedBalance) { const BankProfileInfo app = BankProfileDAO::getApplication(deviceId, appCode); if (app.fullName.trimmed().isEmpty() || app.phone.trimmed().isEmpty()) { qWarning() << "[js:black/helpers] createBankProfileIfNeeded: skipping — fullName or phone is empty"; return; } const QMap currencyCodes = { {"RUB", 643}, {"USD", 840}, {"KRW", 410}, {"AZN", 944}, {"ARS", 32}, {"TJS", 972} }; const int currencyCode = currencyCodes.value(app.currency.toUpper(), 840); const QStringList parts = app.fullName.split(' ', Qt::SkipEmptyParts); const QSettings settings("config.ini", QSettings::IniFormat); const QString phoneCode = settings.value(appCode + "/phone_code").toString(); QString phone = app.phone; phone.remove(QRegularExpression("[^0-9]")); if (!phoneCode.isEmpty() && !phone.startsWith(phoneCode)) { if (phone.startsWith('8') && phoneCode != "8") { phone.replace(0, 1, phoneCode); } else { phone.prepend(phoneCode); } } phone.prepend('+'); const DeviceInfo device = DeviceDAO::getDeviceById(deviceId); QJsonObject profileBody; profileBody["first_name"] = parts.value(0); profileBody["middle_name"] = parts.size() >= 3 ? parts.value(1) : QString(""); profileBody["last_name"] = parts.size() >= 3 ? parts.value(2) : parts.value(1); profileBody["bank_name"] = appCode; profileBody["phone_number"] = phone; const double balance = (parsedBalance >= 0.0) ? parsedBalance : app.amount; profileBody["balance"] = QString::number(qRound64(balance * 100)); profileBody["state"] = (app.status == "active") ? "enabled" : "disabled"; profileBody["device_id"] = device.apiId; profileBody["currency"] = currencyCode; NetworkService ns; ns.setDeviceId(deviceId); ns.setPayload(QJsonDocument(profileBody).toJson()); if (app.bankProfileId.isEmpty()) { ns.addExtra("app_id", app.id); ns.postBankProfile(); } else { ns.addExtra("bank_profile_id", app.bankProfileId); ns.patchBankProfile(); } } } // namespace Scripting