1
0
forked from BRT/arc
arc/scripts/ozon/get_last_transactions.js

666 lines
29 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

// ozon/get_last_transactions.js
//
// JS-порт Ozon::GetLastTransactionsScript. Логика 1:1 с C++ (см.
// android/ozon/GetLastTransactionsScript.cpp). Самый длинный из Ozon-скриптов:
// навигация до "Операции" → либо поиск конкретной транзакции по сумме+времени,
// либо сканирование последних N транзакций.
//
// Контракт:
// params.deviceId — серийный или android_id
// params.appCode — "ozon"
// params.maxCount — 0 = фильтр по 24ч, >0 = N последних
// params.searchAmount — сумма в рублях для поиска (0 = scan-mode)
// params.searchTime — ISO-строка времени (UTC, "" если scan-mode)
// params.materialId — UUID задачи (для дампа)
//
// Особенности порта:
// - таймзону устройства игнорируем, используем MSK (+3ч) — Ozon только в РФ.
// В C++ берётся getDeviceTimezone(), но в 99% случаев это всё равно МСК.
// - host.script.emitTransactionParsed(jsonStr) шлёт результат в EventHandler.
// - OCR для bank_transaction_id (когда XML не отдал) — через host.ocr.
var MSK_OFFSET_MIN = 180; // +03:00
var MONTH_MAP = {
"января": 1, "февраля": 2, "марта": 3, "апреля": 4,
"мая": 5, "июня": 6, "июля": 7, "августа": 8,
"сентября": 9, "октября": 10, "ноября": 11, "декабря": 12
};
function run(host) {
var deviceId = host.params.deviceId;
var appCode = host.params.appCode;
var maxCount = host.params.maxCount | 0;
var searchAmount = +host.params.searchAmount || 0;
var searchTimeIso = host.params.searchTime || "";
var materialId = host.params.materialId || "";
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_TRANSACTIONS", deviceId, {
materialId: materialId, amount: searchAmount,
});
var counter = {n: 0};
try {
if (!navigateToTransactionList(host, device, app, deviceId, counter)) {
var navErr = "Transaction list screen not loaded: " + deviceId;
host.dump.endScope(navErr);
return navErr;
}
var accountId = -1;
var accounts = host.dao.materialGetAccounts(device.id, appCode);
if (accounts.length > 0) accountId = accounts[0].id;
else host.log.warn("No account found for " + device.id + " " + appCode);
var result;
if (Math.abs(searchAmount) > 0.01) {
result = searchTransaction(host, device, deviceId, appCode, accountId,
searchAmount, searchTimeIso, counter);
} else {
result = scanRecentTransactions(host, device, deviceId, appCode, accountId,
maxCount, counter);
}
host.dump.endScope(result || "");
return result;
} catch (e) {
host.dump.endScope(String(e));
throw e;
}
}
// ─── navigateToTransactionList ─────────────────────────────────────────────
function navigateToTransactionList(host, device, app, deviceId, counter) {
function tryRefreshIfError() {
var refreshBtn = host.parser.findNodeByContentDesc
? host.parser.findNodeByContentDesc("Обновить")
: findNodeByDescFallback(host, "Обновить");
if (refreshBtn && refreshBtn.x > 0 && refreshBtn.y > 0) {
host.log.debug("Page load error, tapping 'Обновить'");
host.adb.makeTap(deviceId, refreshBtn.x, refreshBtn.y);
host.timer.sleep(5000);
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n));
}
}
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n));
tryRefreshIfError();
var adNode = host.parser.tryToFindAdBanner();
if (!adNode.isEmpty) {
host.adb.makeTap(deviceId, adNode.x, adNode.y);
host.timer.sleep(1000);
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n));
var stillAd = host.parser.tryToFindAdBanner();
if (!stillAd.isEmpty) {
host.adb.goBack(deviceId);
host.timer.sleep(1000);
}
}
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n));
// Уже на экране операций — скроллим к началу
if (host.parser.isTransactionListScreen()) {
host.log.debug("Already on tx list, scrolling to top");
for (var i = 0; i < 5; ++i) {
host.adb.makeSwipeWithDuration(deviceId,
Math.floor(device.screenWidth / 2), Math.floor(device.screenHeight / 3),
Math.floor(device.screenWidth / 2), Math.floor(device.screenHeight * 2 / 3),
200);
host.timer.sleep(500);
}
host.timer.sleep(1000);
return true;
}
// Не на домашнем → перезапуск
if (!host.parser.isHomeScreen()) {
if (!host.helpers.runAppAndGoToHomeScreen(deviceId, app.package, app.pinCode,
device.screenWidth, device.screenHeight)) {
host.log.warn("Cannot reach home screen");
host.adb.tryToKillApplication(deviceId, app.package);
return false;
}
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n));
tryRefreshIfError();
}
host.helpers.tryToCloseAllBannersOnHomePage(deviceId, device.screenWidth, device.screenHeight);
host.timer.sleep(500);
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n));
// Скроллим до "Все" и тапаем
var navBarTop = device.screenHeight - Math.floor(device.screenHeight / 10);
var allBtn = {isEmpty: true};
for (var j = 0; j < 10; ++j) {
if (host.timer.isInterrupted()) return false;
var x = Math.floor(device.screenWidth / 2);
host.adb.makeSwipeWithDuration(deviceId, x, device.screenHeight - 200, x, 200, 100);
host.timer.sleep(1500);
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n));
host.helpers.closeGooglePlayBannerIfVisible(deviceId, device.screenWidth, device.screenHeight);
allBtn = host.parser.findButtonNode("Все", false);
if (!allBtn.isEmpty && allBtn.y > 0 && allBtn.y < navBarTop) break;
allBtn = {isEmpty: true};
}
if (allBtn.isEmpty) return false;
host.adb.makeTap(deviceId, allBtn.x, allBtn.y);
host.timer.sleep(2000);
// Ждём экран "Операции"
for (var a = 0; a < 10; ++a) {
if (host.timer.isInterrupted()) return false;
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n));
host.helpers.closeGooglePlayBannerIfVisible(deviceId, device.screenWidth, device.screenHeight);
if (host.parser.isTransactionListScreen()) return true;
host.timer.sleep(1500);
}
return false;
}
// Fallback: ищем по content-desc через findAllNodes если bridge не имеет findNodeByContentDesc.
function findNodeByDescFallback(host, desc) {
var all = host.parser.findAllNodes();
for (var i = 0; i < all.length; ++i) {
if (all[i].contentDesc === desc) return all[i];
}
return {isEmpty: true, x: -1, y: -1};
}
// ─── Дата-хелперы ──────────────────────────────────────────────────────────
// Парсит заголовок "Сегодня"/"Вчера"/"DD месяца, день_недели" в Date (00:00 MSK).
function parseDateHeader(text, searchYear) {
var t = (text || "").trim();
var nowMsk = nowMskDate();
if (t.toLowerCase() === "сегодня") return atMidnightMsk(nowMsk);
if (t.toLowerCase() === "вчера") return atMidnightMsk(addDays(nowMsk, -1));
var m = t.match(/(\d{1,2})\s+(\S+),\s+\S+/);
if (!m) return null;
var day = parseInt(m[1], 10);
var monName = m[2].toLowerCase();
var mon = MONTH_MAP[monName] || 0;
if (mon === 0) return null;
return new Date(Date.UTC(searchYear, mon - 1, day) - MSK_OFFSET_MIN * 60 * 1000);
}
function nowMskDate() {
return new Date(Date.now() + MSK_OFFSET_MIN * 60 * 1000); // shift to MSK
}
function atMidnightMsk(d) {
// d уже в MSK-shifted виде
return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate())
- MSK_OFFSET_MIN * 60 * 1000);
}
function addDays(d, n) {
return new Date(d.getTime() + n * 86400000);
}
function sameDate(a, b) {
if (!a || !b) return false;
return a.getTime() === b.getTime();
}
function isBefore(a, b) {
return a && b && a.getTime() < b.getTime();
}
function formatDateMsk(d) {
var msk = new Date(d.getTime() + MSK_OFFSET_MIN * 60 * 1000);
function pad(n) { return n < 10 ? '0' + n : '' + n; }
return pad(msk.getUTCDate()) + "." + pad(msk.getUTCMonth() + 1) + "." + msk.getUTCFullYear();
}
// ─── searchTransaction ─────────────────────────────────────────────────────
function searchTransaction(host, device, deviceId, appCode, accountId,
searchAmount, searchTimeIso, counter) {
var maxScrolls = 30;
var searchTime = searchTimeIso ? new Date(searchTimeIso) : null;
if (!searchTime || isNaN(searchTime.getTime())) return "Invalid searchTime";
var searchDate = atMidnightMsk(new Date(searchTime.getTime() + MSK_OFFSET_MIN * 60 * 1000));
var searchYear = new Date(searchTime.getTime() + MSK_OFFSET_MIN * 60 * 1000).getUTCFullYear();
function detectNavBarTop() {
var nodes = host.parser.findAllNodes();
for (var i = 0; i < nodes.length; ++i) {
var n = nodes[i];
if (n.resourceId === "ru.ozon.fintech.finance:id/main_activity_bottom_navigation_holder"
&& n.height > 0) return n.y1;
}
return device.screenHeight - Math.floor(device.screenHeight / 10);
}
var navBarTop = detectNavBarTop();
host.log.debug("Searching: amount=" + searchAmount + " date=" + formatDateMsk(searchDate));
// Считаем сколько дат на каждой y-координате (для детекции схлопнутых)
function computeDateYCount() {
var cnt = {};
var nodes = host.parser.findAllNodes();
for (var i = 0; i < nodes.length; ++i) {
var n = nodes[i];
if (n.className !== "android.widget.TextView") continue;
if (n.y <= 0) continue;
if (parseDateHeader(n.text, searchYear)) {
cnt[n.y] = (cnt[n.y] || 0) + 1;
}
}
return cnt;
}
function hasDateOnScreen() {
var yc = computeDateYCount();
var nodes = host.parser.findAllNodes();
for (var i = 0; i < nodes.length; ++i) {
var n = nodes[i];
if (n.className !== "android.widget.TextView") continue;
if (n.y <= 0) continue;
if ((yc[n.y] || 0) !== 1) continue;
var d = parseDateHeader(n.text, searchYear);
if (d && sameDate(d, searchDate)) return true;
}
return false;
}
function hasOlderDateOnScreen() {
var yc = computeDateYCount();
var nodes = host.parser.findAllNodes();
for (var i = 0; i < nodes.length; ++i) {
var n = nodes[i];
if (n.className !== "android.widget.TextView") continue;
if (n.y <= 0) continue;
if ((yc[n.y] || 0) !== 1) continue;
var d = parseDateHeader(n.text, searchYear);
if (d && isBefore(d, searchDate)) return true;
}
return false;
}
// Ждём WebView accessibility tree
for (var w = 0; w < 5; ++w) {
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n));
host.helpers.closeGooglePlayBannerIfVisible(deviceId, device.screenWidth, device.screenHeight);
if (host.parser.findTransactionButtons(1).length > 0) break;
if (hasDateOnScreen()) break;
host.timer.sleep(2000);
}
// Скроллим до нужной даты
var dateFound = false;
for (var s = 0; s < maxScrolls; ++s) {
if (host.timer.isInterrupted()) return "Interrupted";
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n));
host.helpers.closeGooglePlayBannerIfVisible(deviceId, device.screenWidth, device.screenHeight);
if (hasDateOnScreen()) { dateFound = true; break; }
if (hasOlderDateOnScreen()) { host.log.debug("Passed target date"); break; }
host.adb.makeSwipeWithDuration(deviceId,
Math.floor(device.screenWidth / 2), Math.floor(device.screenHeight * 2 / 3),
Math.floor(device.screenWidth / 2), Math.floor(device.screenHeight / 3),
600);
host.timer.sleep(1500);
}
if (!dateFound) {
return "Date not found in transaction list: " + formatDateMsk(searchDate);
}
function findDateYRange() {
var yc = computeDateYCount();
function isVisible(y) { return y > 0 && (yc[y] || 0) === 1; }
var dateY = -1;
var nextDateY = device.screenHeight;
var foundOlder = false;
var nodes = host.parser.findAllNodes();
for (var i = 0; i < nodes.length; ++i) {
var n = nodes[i];
if (n.className !== "android.widget.TextView") continue;
var d = parseDateHeader(n.text, searchYear);
if (!d) continue;
if (sameDate(d, searchDate)) {
if (isVisible(n.y)) dateY = n.y;
} else if (isBefore(d, searchDate)) {
foundOlder = true;
if (isVisible(n.y) && (dateY < 0 || n.y > dateY) && n.y < nextDateY) {
nextDateY = n.y;
}
}
}
if (dateY < 0 && nextDateY < device.screenHeight) dateY = 0;
return [dateY, nextDateY];
}
var checked = {};
var noProgress = 0;
for (var attempt = 0; attempt < 15; ++attempt) {
if (host.timer.isInterrupted()) return "Interrupted";
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n));
host.helpers.closeGooglePlayBannerIfVisible(deviceId, device.screenWidth, device.screenHeight);
var range = findDateYRange();
var dateHeaderY = range[0], nextDateHeaderY = range[1];
if (dateHeaderY < 0) {
host.adb.makeSwipeWithDuration(deviceId,
Math.floor(device.screenWidth / 2), Math.floor(device.screenHeight * 2 / 3),
Math.floor(device.screenWidth / 2), Math.floor(device.screenHeight / 3), 500);
host.timer.sleep(1500);
continue;
}
if (dateHeaderY === 0 && nextDateHeaderY < device.screenHeight) {
host.adb.makeSwipeWithDuration(deviceId,
Math.floor(device.screenWidth / 2), Math.floor(device.screenHeight / 3),
Math.floor(device.screenWidth / 2), Math.floor(device.screenHeight * 2 / 3), 600);
host.timer.sleep(1500);
continue;
}
var txButtons = host.parser.findTransactionButtons(20);
var tapped = false, hasNewButton = false, buttonsInRange = 0;
for (var bi = 0; bi < txButtons.length; ++bi) {
if (host.timer.isInterrupted()) return "Interrupted";
var btn = txButtons[bi];
if (btn.y1 <= dateHeaderY || btn.y1 >= nextDateHeaderY) continue;
if (btn.y1 <= 0 || btn.y1 >= navBarTop) continue;
++buttonsInRange;
var parsed = host.parser.parseTransactionButtonText(btn.text);
if (Math.abs(parsed.amount - searchAmount) > 0.01) continue;
var safeMaxY = navBarTop - 20;
var tapY = btn.y;
if (tapY > safeMaxY) tapY = Math.max(btn.y1 + 30, safeMaxY);
host.adb.makeTap(deviceId, btn.x, tapY);
host.timer.sleep(2000);
var detail = {bankTime: ""};
for (var da = 0; da < 10; ++da) {
if (host.timer.isInterrupted()) return "Interrupted";
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n));
host.helpers.closeGooglePlayBannerIfVisible(deviceId, device.screenWidth, device.screenHeight);
detail = host.parser.parseTransactionDetail();
if (detail.bankTime) break;
if (host.parser.isTransactionListScreen()) {
host.adb.makeTap(deviceId, btn.x, btn.y);
}
host.timer.sleep(1500);
}
if (!detail.bankTime) {
host.adb.goBack(deviceId);
host.timer.sleep(1000);
continue;
}
var dtStr = detail.bankTime.replace(/[ ]/g, ' ');
var txTime = parseBankTime(dtStr);
if (checked[dtStr]) {
host.adb.goBack(deviceId);
host.timer.sleep(500);
continue;
}
checked[dtStr] = true;
tapped = true;
hasNewButton = true;
// Дата принадлежит нужной?
if (txTime) {
var txMsk = new Date(txTime.getTime() + MSK_OFFSET_MIN * 60 * 1000);
var txDate = new Date(Date.UTC(txMsk.getUTCFullYear(), txMsk.getUTCMonth(),
txMsk.getUTCDate()) - MSK_OFFSET_MIN * 60 * 1000);
if (!sameDate(txDate, searchDate)) {
host.adb.goBack(deviceId);
host.timer.sleep(1000);
continue;
}
// ±10 минут
if (searchTime) {
var diffSec = Math.abs((txTime.getTime() - searchTime.getTime()) / 1000);
if (diffSec > 600) {
host.adb.goBack(deviceId);
host.timer.sleep(1000);
continue;
}
}
}
host.log.debug("FOUND transaction: amount=" + parsed.amount + " time=" + dtStr);
// Скриншот деталей для receipt
host.adb.captureScreenshotBase64(deviceId);
var externalId = detail.bankTrExternalId || "";
if (accountId !== -1) {
var fallbackId = externalId || ("ozon_" + dtStr.replace(/,/g, '').trim().replace(/ /g, '_'));
var tx = {
accountId: accountId,
amount: parsed.amount,
bankName: appCode,
bankTime: dtStr,
bankTrExternalId: fallbackId,
status: "complete",
timestamp: new Date().toISOString(),
};
if (txTime) tx.completeTime = txTime.toISOString();
if (detail.name) tx.name = detail.name;
if (detail.description) tx.description = detail.description;
if (detail.phone) {
tx.phone = (detail.phone || "").replace(/[^0-9]/g, '');
tx.type = "phone";
}
host.dao.transactionInsert(tx);
}
// Шлём в EventHandler через signalSink
var finalId = externalId || ("ozon_" + dtStr.replace(/,/g, '').trim().replace(/ /g, '_'));
var emitObj = {
bank_transaction_id: finalId,
amount: Math.round(parsed.amount * 100),
currency: 643,
transaction_type: "bank",
bank_participator_id: detail.phone || detail.name || "",
status: "completed",
extra: {fee: 0},
created_at: txTime ? txTime.toISOString() : null,
};
host.script.emitTransactionParsed(JSON.stringify(emitObj));
host.adb.goBack(deviceId);
host.timer.sleep(1000);
host.adb.goBack(deviceId);
host.timer.sleep(1000);
return ""; // success
}
if (!hasNewButton && tapped) {
if (++noProgress >= 5) break;
} else if (hasNewButton) noProgress = 0;
if (!tapped || !hasNewButton) {
var cx = Math.floor(device.screenWidth / 2);
if (dateHeaderY === 0 && nextDateHeaderY < device.screenHeight) {
host.adb.makeSwipeWithDuration(deviceId, cx,
Math.floor(device.screenHeight * 2 / 5), cx,
Math.floor(device.screenHeight * 3 / 4), 600);
host.timer.sleep(1500);
} else {
var safeBottom = navBarTop - 30;
var amplitude;
if (dateHeaderY > 0 && dateHeaderY < safeBottom - 100) {
var safeTop = Math.max(dateHeaderY + 80, Math.floor(device.screenHeight / 4));
amplitude = Math.min(safeBottom - safeTop, 700);
} else {
amplitude = Math.min(safeBottom - Math.floor(device.screenHeight / 4), 700);
}
amplitude = Math.max(amplitude, 300);
for (var sw = 0; sw < 3; ++sw) {
host.adb.makeSwipeWithDuration(deviceId, cx, safeBottom,
cx, safeBottom - amplitude, 250);
host.timer.sleep(350);
}
host.timer.sleep(800);
}
}
}
return "Transaction not found: amount=" + searchAmount.toFixed(2)
+ " date=" + formatDateMsk(searchDate);
}
// Парсит "dd.MM.yyyy, HH:mm" или "dd.MM.yyyy,HH:mm" → UTC Date (MSK-localized).
function parseBankTime(s) {
var m = (s || "").match(/(\d{2})\.(\d{2})\.(\d{4}),?\s*(\d{2}):(\d{2})/);
if (!m) return null;
var d = parseInt(m[1], 10), mo = parseInt(m[2], 10), y = parseInt(m[3], 10);
var hh = parseInt(m[4], 10), mm = parseInt(m[5], 10);
return new Date(Date.UTC(y, mo - 1, d, hh, mm) - MSK_OFFSET_MIN * 60 * 1000);
}
// ─── scanRecentTransactions ────────────────────────────────────────────────
function scanRecentTransactions(host, device, deviceId, appCode, accountId, maxCount, counter) {
var navBarTop = device.screenHeight - Math.floor(device.screenHeight / 10);
// Ждём появления транзакций
var txButtons = [];
for (var i = 0; i < 5; ++i) {
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n));
host.helpers.closeGooglePlayBannerIfVisible(deviceId, device.screenWidth, device.screenHeight);
txButtons = host.parser.findTransactionButtons(8);
if (txButtons.length > 0) break;
host.timer.sleep(1500);
}
var transactions = [];
for (var i2 = 0; i2 < txButtons.length; ++i2) {
var btn = txButtons[i2];
if (btn.y <= 0 || btn.y >= navBarTop) {
for (var s = 0; s < 5; ++s) {
var x = Math.floor(device.screenWidth / 2);
var offset = Math.floor(device.screenHeight / 4);
host.adb.makeSwipe(deviceId, x, device.screenHeight - offset, x, offset);
host.timer.sleep(1000);
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n));
host.helpers.closeGooglePlayBannerIfVisible(deviceId, device.screenWidth, device.screenHeight);
txButtons = host.parser.findTransactionButtons(8);
if (i2 < txButtons.length && txButtons[i2].y > 0 && txButtons[i2].y < navBarTop) break;
}
if (i2 >= txButtons.length) break;
}
var visibleBtn = txButtons[i2];
var tx = host.parser.parseTransactionButtonText(visibleBtn.text);
host.adb.makeTap(deviceId, visibleBtn.x, visibleBtn.y);
host.timer.sleep(2000);
var detail = {bankTime: ""};
for (var da = 0; da < 10; ++da) {
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n));
host.helpers.closeGooglePlayBannerIfVisible(deviceId, device.screenWidth, device.screenHeight);
detail = host.parser.parseTransactionDetail();
if (detail.bankTime) break;
if (host.parser.isTransactionListScreen()) {
host.adb.makeTap(deviceId, visibleBtn.x, visibleBtn.y);
}
host.timer.sleep(1500);
}
tx.bankName = appCode;
tx.bankTime = detail.bankTime;
tx.bankTrExternalId = detail.bankTrExternalId || "";
// Если ID не найден — пробуем через "Документы" + OCR
if (!tx.bankTrExternalId) {
var docBtn = host.parser.findButtonNode("Документы", false);
if (!docBtn.isEmpty) {
host.adb.makeTap(deviceId, docBtn.x, docBtn.y);
host.timer.sleep(2000);
var docLoaded = false;
for (var di = 0; di < 15; ++di) {
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n));
host.helpers.closeGooglePlayBannerIfVisible(deviceId, device.screenWidth, device.screenHeight);
if (!findNodeByDescFallback(host, "Поделиться").isEmpty) {
docLoaded = true; break;
}
host.timer.sleep(1500);
}
if (docLoaded) {
host.timer.sleep(500);
var ocrText = host.ocr.recognizeFromScreen(deviceId);
if (ocrText) {
tx.bankTrExternalId = host.ocr.extractTransactionId(ocrText) || "";
}
}
host.adb.goBack(deviceId);
host.timer.sleep(1000);
}
}
tx.status = "complete";
tx.timestamp = new Date().toISOString();
if (detail.name) tx.name = detail.name;
if (detail.phone) {
tx.phone = (detail.phone || "").replace(/[^0-9]/g, '');
tx.type = "phone";
}
if (tx.bankTime) {
var dtStr = tx.bankTime.replace(/[ ]/g, ' ');
tx.bankTime = dtStr;
var parsed = parseBankTime(dtStr);
if (parsed) tx.completeTime = parsed.toISOString();
}
// Фильтр по времени (24ч)
if (maxCount <= 0 && tx.completeTime) {
var ageSec = (Date.now() - new Date(tx.completeTime).getTime()) / 1000;
if (ageSec > 86400) {
host.adb.goBack(deviceId);
host.timer.sleep(1000);
break;
}
}
if (accountId !== -1) {
tx.accountId = accountId;
host.dao.transactionInsert(tx);
// Receipt + OCR-id через "Документы" — упрощённо, без двойного перехода.
// Если уже извлекли ID выше — не повторяем.
}
transactions.push(tx);
if (maxCount > 0 && transactions.length >= maxCount) {
host.adb.goBack(deviceId);
host.timer.sleep(1000);
break;
}
host.adb.goBack(deviceId);
host.timer.sleep(1500);
for (var wa = 0; wa < 5; ++wa) {
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n));
host.helpers.closeGooglePlayBannerIfVisible(deviceId, device.screenWidth, device.screenHeight);
if (host.parser.isTransactionListScreen()) break;
host.timer.sleep(1000);
}
host.parser.parseAndSaveXml(host.adb.getScreenDumpAsXml(deviceId, ++counter.n));
txButtons = host.parser.findTransactionButtons(8);
}
return ""; // success
}