#include "PayByPhoneScript.h" #include #include #include #include #include "adb/AdbUtils.h" #include "dao/AccountDAO.h" #include "dao/ApplicationDAO.h" #include "dao/DeviceDAO.h" #include "dao/TransactionDAO.h" namespace Ozon { 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::getApplication(m_account.deviceId, m_account.appCode); if (device.id.isEmpty() || app.id == -1) { m_error = "Device or app not found: " + m_account.deviceId; qCritical() << m_error; emit finishedWithResult(m_error); return; } xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter)); // Закрываем рекламный bottom sheet if (Node adNode = xmlScreenParser.tryToFindAdBanner(); !adNode.isEmpty()) { qDebug() << "[PayByPhone] Ad banner detected, closing..."; AdbUtils::makeTap(device.id, adNode.x(), adNode.y()); QThread::msleep(1000); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter)); } // Если не на домашнем экране — перезапускаем if (!xmlScreenParser.isHomeScreen()) { qDebug() << "[PayByPhone] Not on home screen, restarting app..."; if (!runAppAndGoToHomeScreen(device.id, app.package, app.pinCode, device.screenWidth, device.screenHeight)) { m_error = "Cannot reach home screen: " + device.id; qWarning() << m_error; AdbUtils::tryToKillApplication(device.id, app.package); emit finishedWithResult(m_error); return; } xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter)); } // Закрываем баннеры tryToCloseAllBannersOnHomePage(device.id, device.screenWidth, device.screenHeight); QThread::msleep(500); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter)); // Закрываем баннер Google Play "Доступно обновление" if (Node gpNode = xmlScreenParser.tryToFindGooglePlayBanner(); !gpNode.isEmpty()) { qDebug() << "[PayByPhone] Google Play update banner detected, closing..."; AdbUtils::makeTap(device.id, gpNode.x(), gpNode.y()); QThread::msleep(1500); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter)); } // Закрываем баннер "Заказать карту" — тапаем сверху экрана if (Node orderCardNode = xmlScreenParser.findTextNode("Заказать карту"); !orderCardNode.isEmpty()) { qDebug() << "[PayByPhone] 'Заказать карту' banner detected, closing..."; AdbUtils::makeTap(device.id, device.screenWidth / 2, 100); QThread::msleep(1000); xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter)); } makePayment(device.id, app.pinCode, device.screenWidth, device.screenHeight); emit finishedWithResult(m_error); } void PayByPhoneScript::makePayment( const QString &deviceId, const QString &pinCode, const int width, const int height ) { // 1. Нажимаем "Перевести" на домашнем экране Node transferBtn = xmlScreenParser.findButtonNode("Перевести", false); if (transferBtn.isEmpty()) { m_error = "Кнопка 'Перевести' не найдена на домашнем экране"; qWarning() << "[PayByPhone]" << m_error; return; } qDebug() << "[PayByPhone] Tapping 'Перевести'..."; AdbUtils::makeTap(deviceId, transferBtn.x(), transferBtn.y()); QThread::msleep(2000); // 2. Ждём экран "Платежи и переводы" с полем "Телефон получателя" bool paymentsPageLoaded = false; for (int attempt = 0; attempt < 10; ++attempt) { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); Node phoneField = xmlScreenParser.findFirstEditText(); if (!phoneField.isEmpty()) { paymentsPageLoaded = true; break; } qDebug() << "[PayByPhone] Waiting for payments page, attempt" << attempt + 1 << "/ 10"; QThread::msleep(1500); } if (!paymentsPageLoaded) { m_error = "Экран 'Платежи и переводы' не загрузился"; qWarning() << "[PayByPhone]" << m_error; return; } // 3. Тапаем на поле "Телефон получателя" чтобы перейти на экран "Кому перевести" Node phoneFieldTap = xmlScreenParser.findFirstEditText(); qDebug() << "[PayByPhone] Tapping 'Телефон получателя'..."; AdbUtils::makeTap(deviceId, phoneFieldTap.x(), phoneFieldTap.y()); QThread::msleep(2000); // 4. Ждём экран "Кому перевести" bool phoneScreenLoaded = false; for (int attempt = 0; attempt < 10; ++attempt) { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); Node title = xmlScreenParser.findTextNode("Кому перевести"); if (!title.isEmpty()) { phoneScreenLoaded = true; break; } qDebug() << "[PayByPhone] Waiting for 'Кому перевести', attempt" << attempt + 1 << "/ 10"; QThread::msleep(1500); } if (!phoneScreenLoaded) { m_error = "Экран 'Кому перевести' не загрузился"; qWarning() << "[PayByPhone]" << m_error; return; } // 5. Тапаем на EditText и вводим номер телефона Node phoneInput; for (int attempt = 0; attempt < 10; ++attempt) { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); phoneInput = xmlScreenParser.findFirstEditText(); if (!phoneInput.isEmpty()) break; qDebug() << "[PayByPhone] Waiting for phone input field, attempt" << attempt + 1 << "/ 10"; QThread::msleep(1500); } if (phoneInput.isEmpty()) { m_error = "Поле ввода телефона не найдено"; qWarning() << "[PayByPhone]" << m_error; return; } AdbUtils::makeTap(deviceId, phoneInput.x(), phoneInput.y()); QThread::msleep(500); AdbUtils::inputText(deviceId, m_phone); QThread::msleep(2000); // 6. Ждём экран "По номеру телефона", обрабатывая промежуточные экраны bool sumScreenLoaded = false; bool bankSelected = false; bool otherBankClicked = false; for (int attempt = 0; attempt < 20; ++attempt) { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); // Проверяем: уже на экране суммы? Node sumTitle = xmlScreenParser.findTextNode("По номеру телефона"); if (!sumTitle.isEmpty() && sumTitle.y() < 300) { sumScreenLoaded = true; break; } // Ищем кнопку с номером телефона (новый перевод / последние переводы) QString phoneDigits = m_phone; phoneDigits.remove(QRegularExpression("[^0-9]")); bool foundPhoneBtn = false; for (const Node &btn : xmlScreenParser.findAllButtons()) { QString btnDigits = btn.text; btnDigits.remove(QRegularExpression("[^0-9]")); if (btnDigits.contains(phoneDigits) || phoneDigits.contains(btnDigits.right(10))) { qDebug() << "[PayByPhone] Found button with phone number:" << btn.text; AdbUtils::makeTap(deviceId, btn.x(), btn.y()); QThread::msleep(2000); foundPhoneBtn = true; break; } } if (foundPhoneBtn) continue; // Экран выбора банка: ищем "Другой банк" (текст) или EditText с hint "Название банка" if (!bankSelected) { // Вариант 1: кнопка "Другой банк" (content-desc="other-bank-item", text пустой) bool foundOtherBank = false; { Node node; for (const Node &n : xmlScreenParser.findAllNodes()) { if (n.contentDesc.contains("other-bank-item")) { node = n; break; } } if (!node.isEmpty()) { qDebug() << "[PayByPhone] Tapping 'Другой банк' (other-bank-item)..."; AdbUtils::makeTap(deviceId, node.x(), node.y()); QThread::msleep(2000); foundOtherBank = true; otherBankClicked = true; } } if (foundOtherBank) continue; // Вариант 2: уже на экране поиска банка (EditText) — только после клика на "Другой банк" if (otherBankClicked) { Node bankInput = xmlScreenParser.findFirstEditText(); if (!bankInput.isEmpty()) { qDebug() << "[PayByPhone] Bank search screen detected, entering bank name..."; AdbUtils::makeTap(deviceId, bankInput.x(), bankInput.y()); QThread::msleep(500); // Ввод кириллицы через ADB Keyboard AdbUtils::enableAdbIMEKeyboard(deviceId); QThread::msleep(500); AdbUtils::makeTap(deviceId, bankInput.x(), bankInput.y()); QThread::msleep(500); AdbUtils::inputAdbIMEText(deviceId, m_bankName); QThread::msleep(2000); AdbUtils::disableAdbIMEKeyboard(deviceId); qDebug() << "[PayByPhone] Entered bank name via ADB IME:" << m_bankName; QThread::msleep(2000); // Ищем банк в списке и кликаем по нему (кнопка с числовым content-desc) bool bankFound = false; for (int ba = 0; ba < 10; ++ba) { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); for (const Node &node : xmlScreenParser.findAllNodes()) { if (node.className == "android.widget.Button" && !node.contentDesc.isEmpty() && node.contentDesc != "base-go-back-button" && node.contentDesc != "native-toolbar-close-button" && node.contentDesc != "clear-input-icon") { qDebug() << "[PayByPhone] Found bank in list, tapping:" << node.contentDesc << "bounds:" << node.x() << node.y(); AdbUtils::makeTap(deviceId, node.x(), node.y()); bankFound = true; break; } } if (bankFound) break; qDebug() << "[PayByPhone] Waiting for bank list, attempt" << ba + 1 << "/ 10"; QThread::msleep(1500); } if (!bankFound) { m_error = "Банк '" + m_bankName + "' не найден в списке"; qWarning() << "[PayByPhone]" << m_error; return; } bankSelected = true; QThread::msleep(2000); continue; } } // otherBankClicked } qDebug() << "[PayByPhone] Waiting for 'По номеру телефона', attempt" << attempt + 1 << "/ 20"; QThread::msleep(1500); } if (!sumScreenLoaded) { m_error = "Экран ввода суммы не загрузился"; qWarning() << "[PayByPhone]" << m_error; return; } // 7. Вводим сумму Node sumInput = xmlScreenParser.findFirstEditText(); if (sumInput.isEmpty()) { m_error = "Поле ввода суммы не найдено"; qWarning() << "[PayByPhone]" << m_error; return; } AdbUtils::makeTap(deviceId, sumInput.x(), sumInput.y()); QThread::msleep(500); AdbUtils::inputText(deviceId, QString::number(static_cast(m_amount))); QThread::msleep(1000); // 6. Нажимаем "Продолжить" xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); Node continueBtn = xmlScreenParser.findButtonNode("Продолжить", false); if (continueBtn.isEmpty()) { m_error = "Кнопка 'Продолжить' не найдена"; qWarning() << "[PayByPhone]" << m_error; return; } AdbUtils::makeTap(deviceId, continueBtn.x(), continueBtn.y()); QThread::msleep(2000); // 7. Ждём экран "Подтвердите перевод" с кнопкой "Перевести ..." bool confirmScreenLoaded = false; for (int attempt = 0; attempt < 10; ++attempt) { xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter)); Node confirmTitle = xmlScreenParser.findTextNode("Подтвердите перевод"); if (!confirmTitle.isEmpty()) { confirmScreenLoaded = true; break; } qDebug() << "[PayByPhone] Waiting for 'Подтвердите перевод', attempt" << attempt + 1 << "/ 10"; QThread::msleep(1500); } if (!confirmScreenLoaded) { m_error = "Экран подтверждения не загрузился"; qWarning() << "[PayByPhone]" << m_error; return; } qDebug() << "[PayByPhone] Confirm screen loaded, stopping before confirmation"; } } // namespace Ozon