359 lines
17 KiB
JavaScript
359 lines
17 KiB
JavaScript
// ozon/get_card_info.js
|
||
//
|
||
// JS-порт Ozon::GetCardInfoScript. Логика 1:1 с C++ (см.
|
||
// android/ozon/GetCardInfoScript.cpp). Самый длинный из Ozon-скриптов:
|
||
// сканирует все карты с home screen, для каждой раскрывает полный номер
|
||
// через "Показать" с retry-логикой и постит на сервер.
|
||
//
|
||
// Контракт:
|
||
// params.deviceId — серийный или android_id
|
||
// params.appCode — "ozon"
|
||
// params.lastNumbers — если задан, обрабатывается только одна карта
|
||
// Возвращает:
|
||
// "" / null / undefined → success
|
||
// строка → бизнес-ошибка
|
||
|
||
function run(host) {
|
||
var deviceId = host.params.deviceId;
|
||
var appCode = host.params.appCode;
|
||
var lastNumbers = host.params.lastNumbers || "";
|
||
var counter = 0;
|
||
|
||
var device = host.dao.deviceGetById(deviceId);
|
||
if (!device || !device.id) {
|
||
device = host.dao.deviceFindByAdbSerial(deviceId);
|
||
}
|
||
if (device && device.adbSerial) {
|
||
deviceId = device.adbSerial;
|
||
}
|
||
var app = host.dao.bankProfileGet(device.id, appCode);
|
||
if (!device || !device.id || !app || app.id < 0) {
|
||
var msg = "Device or app not found: " + deviceId + " " + appCode;
|
||
host.log.error(msg);
|
||
return msg;
|
||
}
|
||
|
||
host.dump.beginScope("ozon", "FETCH_CARDS", deviceId, {});
|
||
|
||
try {
|
||
// 1. Читаем текущий экран
|
||
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
|
||
|
||
// 2. Закрываем рекламный bottom sheet, если открыт
|
||
var adNode = host.parser.tryToFindAdBanner();
|
||
if (!adNode.isEmpty) {
|
||
host.log.debug("Ad banner detected, closing...");
|
||
host.adb.makeTap(deviceId, adNode.x, adNode.y);
|
||
host.timer.sleep(1000);
|
||
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
|
||
}
|
||
|
||
// 3. Свежий дамп перед проверкой домашнего экрана
|
||
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
|
||
|
||
// 4. Если не на домашнем — перезапускаем приложение
|
||
if (!host.parser.isHomeScreen()) {
|
||
host.log.debug("Not on home screen, restarting app...");
|
||
if (!host.helpers.runAppAndGoToHomeScreen(deviceId, app.package, app.pinCode,
|
||
device.screenWidth, device.screenHeight)) {
|
||
var err = "Cannot reach home screen: " + device.name + " (" + deviceId + ")";
|
||
host.log.warn(err);
|
||
host.adb.tryToKillApplication(deviceId, app.package);
|
||
return err;
|
||
}
|
||
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
|
||
}
|
||
|
||
// 5. Закрываем баннеры
|
||
host.helpers.tryToCloseAllBannersOnHomePage(deviceId, device.screenWidth, device.screenHeight);
|
||
host.timer.sleep(500);
|
||
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
|
||
|
||
// 6. Парсим имя/телефон, если в БД ещё нет
|
||
var hasProfileInfo = !!(app.fullName) && !!(app.phone);
|
||
if (!hasProfileInfo) {
|
||
var userNameNode = host.parser.findUserNameOnHomeScreen(device.screenHeight);
|
||
if (!userNameNode.isEmpty) {
|
||
host.log.debug("Navigating to profile to parse name/phone...");
|
||
host.adb.makeTap(deviceId, userNameNode.x, userNameNode.y);
|
||
host.timer.sleep(2500);
|
||
|
||
for (var a = 0; a < 5; ++a) {
|
||
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
|
||
if (host.parser.isProfileScreen()) break;
|
||
host.timer.sleep(1000);
|
||
}
|
||
|
||
if (host.parser.isProfileScreen()) {
|
||
var fullName = host.parser.parseProfileFullName();
|
||
var phone = host.parser.parseProfilePhone();
|
||
host.log.debug("Profile parsed - fullName: " + fullName + " phone: " + phone);
|
||
host.dao.bankProfileUpdateProfileInfo(app.id, fullName, phone, "");
|
||
} else {
|
||
host.log.warn("Profile screen not reached, skipping profile parse");
|
||
}
|
||
|
||
host.adb.goBack(deviceId);
|
||
host.timer.sleep(1500);
|
||
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
|
||
} else {
|
||
host.log.debug("Username not found on home, skipping profile parse");
|
||
}
|
||
} else {
|
||
host.log.debug("Profile info already in DB, skipping profile parse");
|
||
}
|
||
|
||
// 7. Проверяем что снова на домашнем экране
|
||
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
|
||
if (!host.parser.isHomeScreen()) {
|
||
host.log.debug("Not on home screen before card search, restarting...");
|
||
if (!host.helpers.runAppAndGoToHomeScreen(deviceId, app.package, app.pinCode,
|
||
device.screenWidth, device.screenHeight)) {
|
||
var err2 = "Cannot reach home screen before card search: " + device.name + " (" + deviceId + ")";
|
||
host.log.warn(err2);
|
||
host.adb.tryToKillApplication(deviceId, app.package);
|
||
return err2;
|
||
}
|
||
host.helpers.tryToCloseAllBannersOnHomePage(deviceId, device.screenWidth, device.screenHeight);
|
||
host.timer.sleep(500);
|
||
}
|
||
|
||
// 8. Ждём пока карты подгрузятся
|
||
var cards = [];
|
||
for (var attempt = 0; attempt < 5; ++attempt) {
|
||
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
|
||
cards = host.parser.parseCardsOnHomeScreen();
|
||
if (cards.length > 0) break;
|
||
host.log.debug("No cards found on home screen, attempt " + (attempt + 1) + "/5");
|
||
host.timer.sleep(1500);
|
||
}
|
||
|
||
if (cards.length === 0) {
|
||
var noCardsErr = "No cards found on home screen: " + deviceId;
|
||
host.log.warn(noCardsErr);
|
||
host.adb.tryToKillApplication(deviceId, app.package);
|
||
return noCardsErr;
|
||
}
|
||
host.log.debug("Found " + cards.length + " card(s) on home screen");
|
||
|
||
// 9. Апсёртим аккаунты
|
||
for (var ci = 0; ci < cards.length; ++ci) {
|
||
cards[ci].deviceId = device.id;
|
||
cards[ci].appCode = appCode;
|
||
cards[ci].status = "active";
|
||
if (!host.dao.materialUpsert(cards[ci])) {
|
||
host.log.warn("Failed to upsert account for card " + cards[ci].lastNumbers);
|
||
} else {
|
||
host.log.debug("Upserted account: " + cards[ci].lastNumbers
|
||
+ " " + cards[ci].description + " " + cards[ci].amount);
|
||
}
|
||
}
|
||
|
||
// 10. Баланс
|
||
var totalAmount = cards[0].amount;
|
||
if (totalAmount > 0.0) {
|
||
host.dao.bankProfileUpdateAmount(app.id, totalAmount);
|
||
}
|
||
|
||
// 11. createDevice если нет apiId
|
||
host.helpers.createDeviceIfNeeded(device);
|
||
|
||
// 12. Регистрируем/обновляем bank profile на сервере
|
||
host.helpers.createBankProfileIfNeeded(deviceId, appCode, totalAmount);
|
||
|
||
// 13. Для каждой карты раскрываем полный номер через "Показать"
|
||
for (var i = 0; i < cards.length; ++i) {
|
||
var card = cards[i];
|
||
|
||
if (lastNumbers && card.lastNumbers !== lastNumbers) continue;
|
||
host.log.debug("Getting card number for " + card.lastNumbers);
|
||
|
||
var existing = host.dao.materialFindAppAccount(device.id, appCode, card.lastNumbers);
|
||
if (existing && existing.id >= 0 && existing.cardNumber) {
|
||
host.log.debug("Card " + card.lastNumbers + " already has number, skipping");
|
||
continue;
|
||
}
|
||
|
||
var cardBtn = host.parser.findCardButtonByLastNumbers(card.lastNumbers);
|
||
if (cardBtn.isEmpty) {
|
||
host.log.warn("Card button not found for " + card.lastNumbers);
|
||
continue;
|
||
}
|
||
|
||
host.adb.makeTap(deviceId, cardBtn.x, cardBtn.y);
|
||
|
||
// 13a. Скроллим WebView вверх, пока "Показать" не окажется в верхней трети экрана
|
||
var targetTopY = Math.floor(device.screenHeight / 3);
|
||
for (var s = 0; s < 6; ++s) {
|
||
host.timer.sleep(s === 0 ? 2000 : 600);
|
||
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
|
||
|
||
var cand = host.parser.findButtonNode("Показать", false);
|
||
if (cand.isEmpty) {
|
||
if (host.parser.isHomeScreen()) {
|
||
var rb = host.parser.findCardButtonByLastNumbers(card.lastNumbers);
|
||
if (!rb.isEmpty) {
|
||
host.log.debug("Still on home before scroll, re-tapping card " + card.lastNumbers);
|
||
host.adb.makeTap(deviceId, rb.x, rb.y);
|
||
}
|
||
}
|
||
continue;
|
||
}
|
||
if (cand.y <= targetTopY) {
|
||
host.log.debug("'Показать' already at top y=" + cand.y);
|
||
break;
|
||
}
|
||
var swipeFromY = cand.y;
|
||
var swipeToY = Math.floor(device.screenHeight / 6);
|
||
host.log.debug("Scrolling 'Показать' up: " + swipeFromY + " -> " + swipeToY);
|
||
host.adb.makeSwipe(deviceId, Math.floor(device.screenWidth / 2), swipeFromY,
|
||
Math.floor(device.screenWidth / 2), swipeToY);
|
||
}
|
||
|
||
// 13b. Ждём стабильной геометрии: "Показать" в одной строке с "Реквизиты карты"
|
||
// И координаты не меняются в двух подряд дампах.
|
||
var showBtn = null;
|
||
var prevShowY = -1;
|
||
for (var w = 0; w < 10; ++w) {
|
||
host.timer.sleep(w === 0 ? 2000 : 500);
|
||
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
|
||
|
||
var c = host.parser.findButtonNode("Показать", false);
|
||
var req = host.parser.findTextNode("Реквизиты карты");
|
||
|
||
if (c.isEmpty || req.isEmpty) {
|
||
if (host.parser.isHomeScreen()) {
|
||
var rb2 = host.parser.findCardButtonByLastNumbers(card.lastNumbers);
|
||
if (!rb2.isEmpty) {
|
||
host.log.debug("Still on home screen, re-tapping card " + card.lastNumbers);
|
||
host.adb.makeTap(deviceId, rb2.x, rb2.y);
|
||
}
|
||
}
|
||
prevShowY = -1;
|
||
host.log.debug("Requisites not ready, attempt " + (w + 1) + "/10");
|
||
continue;
|
||
}
|
||
|
||
if (Math.abs(c.y1 - req.y1) > 10) {
|
||
prevShowY = -1;
|
||
host.log.debug("'Показать' not aligned with 'Реквизиты карты', waiting reflow");
|
||
continue;
|
||
}
|
||
|
||
if (prevShowY >= 0 && Math.abs(c.y - prevShowY) <= 5) {
|
||
showBtn = c;
|
||
break;
|
||
}
|
||
prevShowY = c.y;
|
||
host.log.debug("'Показать' seen at y=" + c.y + ", waiting for stable frame");
|
||
}
|
||
|
||
if (!showBtn) {
|
||
host.log.warn("'Показать' not stable for card " + card.lastNumbers);
|
||
host.adb.goBack(deviceId);
|
||
host.timer.sleep(1500);
|
||
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
|
||
continue;
|
||
}
|
||
|
||
host.log.debug("'Показать' at " + showBtn.x + " " + showBtn.y);
|
||
host.adb.makeTap(deviceId, showBtn.x, showBtn.y);
|
||
|
||
// 13c. Ждём появления полного номера карты
|
||
var cardNumber = "";
|
||
for (var t = 0; t < 10; ++t) {
|
||
host.timer.sleep(2000);
|
||
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
|
||
cardNumber = host.parser.findFullCardNumber();
|
||
if (cardNumber) {
|
||
host.log.debug("Card number found on attempt " + (t + 1));
|
||
break;
|
||
}
|
||
|
||
// Баннер "Понятно" — закрываем и повторно тапаем "Показать"
|
||
var banner = host.parser.findTextNode("Понятно");
|
||
if (!banner.isEmpty) {
|
||
host.log.debug("Banner 'Понятно' detected, closing...");
|
||
host.adb.makeTap(deviceId, banner.x, banner.y);
|
||
host.timer.sleep(1500);
|
||
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
|
||
var retry = host.parser.findButtonNode("Показать", false);
|
||
if (!retry.isEmpty) {
|
||
host.log.debug("Re-tapping 'Показать' after banner close");
|
||
host.adb.makeTap(deviceId, retry.x, retry.y);
|
||
}
|
||
continue;
|
||
}
|
||
|
||
// "Подождите" — загрузка идёт
|
||
if (!host.parser.findButtonNode("Подождите", false).isEmpty) {
|
||
host.log.debug("'Подождите' visible, waiting... attempt " + (t + 1) + "/10");
|
||
continue;
|
||
}
|
||
|
||
// "Скрыть" видна — номер должен быть, но парсер не нашёл
|
||
if (!host.parser.findButtonNode("Скрыть", false).isEmpty) {
|
||
host.log.debug("'Скрыть' visible but card number not parsed, attempt " + (t + 1) + "/10");
|
||
continue;
|
||
}
|
||
|
||
// Повторный тап "Показать"
|
||
var retry2 = host.parser.findButtonNode("Показать", false);
|
||
if (!retry2.isEmpty) {
|
||
host.log.debug("Re-tapping 'Показать', attempt " + (t + 1) + "/10");
|
||
host.adb.makeTap(deviceId, retry2.x, retry2.y);
|
||
} else {
|
||
host.log.debug("Card number not visible, attempt " + (t + 1) + "/10");
|
||
}
|
||
}
|
||
|
||
if (cardNumber) {
|
||
host.log.debug("Card " + card.lastNumbers + " full number: " + cardNumber);
|
||
var saved = host.dao.materialFindAppAccount(device.id, appCode, card.lastNumbers);
|
||
if (saved && saved.id >= 0) {
|
||
host.dao.materialUpdateCardNumber(saved.id, cardNumber);
|
||
}
|
||
} else {
|
||
var noNumErr = "Card number not found for " + card.lastNumbers;
|
||
host.log.warn(noNumErr);
|
||
var s2 = host.dao.materialFindAppAccount(device.id, appCode, card.lastNumbers);
|
||
if (s2 && s2.id >= 0) {
|
||
host.dao.materialUpdateAccountById(s2.id, "off", s2.description, s2.currency);
|
||
}
|
||
}
|
||
|
||
// Возвращаемся на домашний для следующей карты
|
||
if (i < cards.length - 1) {
|
||
host.adb.goBack(deviceId);
|
||
host.timer.sleep(2000);
|
||
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter));
|
||
}
|
||
}
|
||
|
||
// 14. Отправляем материалы на сервер — после того как все номера в БД
|
||
var updatedApp = host.dao.bankProfileGet(device.id, appCode);
|
||
if (updatedApp && updatedApp.bankProfileId) {
|
||
for (var k = 0; k < cards.length; ++k) {
|
||
var dbCard = host.dao.materialFindAppAccount(device.id, appCode, cards[k].lastNumbers);
|
||
if (!dbCard || dbCard.id < 0) continue;
|
||
if (!dbCard.cardNumber) continue;
|
||
if (dbCard.materialId) {
|
||
host.log.debug("material already exists for card " + dbCard.lastNumbers + ", skipping");
|
||
continue;
|
||
}
|
||
host.helpers.postMaterial(dbCard, updatedApp.bankProfileId, updatedApp.fullName);
|
||
host.log.debug("postMaterial done for card " + dbCard.lastNumbers);
|
||
}
|
||
} else {
|
||
host.log.warn("bankProfileId empty, skipping materials");
|
||
}
|
||
|
||
host.adb.tryToKillApplication(deviceId, app.package);
|
||
host.dump.endScope("");
|
||
return ""; // success
|
||
} catch (e) {
|
||
host.dump.endScope(String(e));
|
||
throw e;
|
||
}
|
||
}
|