1
0
forked from BRT/arc

Restrict OCR search area to the lower half of the confirm sheet in PayByPhoneScript and PayByCardScript to reduce false positives. Adjust minimum word length for substring matching in OcrUtils to improve tap accuracy.

This commit is contained in:
slava 2026-05-21 16:12:20 +05:00
parent 9da2a6fc52
commit 3670e34938
3 changed files with 21 additions and 5 deletions

View File

@ -382,10 +382,14 @@ void PayByCardScript::makePayment(
// отдельного узла "Подтверждаю" в дампе нет — координаты берём OCR-ом // отдельного узла "Подтверждаю" в дампе нет — координаты берём OCR-ом
// по скриншоту. Если OCR не нашёл — fallback на геометрию (центр // по скриншоту. Если OCR не нашёл — fallback на геометрию (центр
// нижней четверти sheet, выше системного nav-бара). // нижней четверти sheet, выше системного nav-бара).
// Compose отдаёт sheet как одну ноду на весь экран ([0,0][720,1640]),
// поэтому confirmNode.y1/y2 не сужают поиск — OCR сканирует весь
// скриншот и ловит ложные совпадения. Ограничиваем нижней половиной.
const int ocrYMin = (confirmNode.y1() + confirmNode.y2()) / 2;
const QByteArray screen = AdbUtils::captureScreenshotBytes(deviceId); const QByteArray screen = AdbUtils::captureScreenshotBytes(deviceId);
QPoint tap = OcrUtils::findTextCenter(screen, QPoint tap = OcrUtils::findTextCenter(screen,
QString::fromUtf8("Подтверждаю"), QString::fromUtf8("Подтверждаю"),
confirmNode.y1(), confirmNode.y2()); ocrYMin, confirmNode.y2());
if (tap.x() < 0) { if (tap.x() < 0) {
const int tapY = confirmNode.y2() - qMax(150, height / 14); const int tapY = confirmNode.y2() - qMax(150, height / 14);
tap = QPoint(confirmNode.x(), tapY); tap = QPoint(confirmNode.x(), tapY);
@ -478,10 +482,11 @@ void PayByCardScript::makePayment(
++confirmRetries; ++confirmRetries;
qDebug() << "[Dushanbe::PayByCard] Confirm sheet still visible, re-tap" qDebug() << "[Dushanbe::PayByCard] Confirm sheet still visible, re-tap"
<< confirmRetries << "/" << maxConfirmRetries; << confirmRetries << "/" << maxConfirmRetries;
const int ocrYMin = (retryConfirm.y1() + retryConfirm.y2()) / 2;
const QByteArray screen = AdbUtils::captureScreenshotBytes(deviceId); const QByteArray screen = AdbUtils::captureScreenshotBytes(deviceId);
QPoint tap = OcrUtils::findTextCenter(screen, QPoint tap = OcrUtils::findTextCenter(screen,
QString::fromUtf8("Подтверждаю"), QString::fromUtf8("Подтверждаю"),
retryConfirm.y1(), retryConfirm.y2()); ocrYMin, retryConfirm.y2());
if (tap.x() < 0) { if (tap.x() < 0) {
// Варьируем Y: первый retry — выше fallback на 100px, // Варьируем Y: первый retry — выше fallback на 100px,
// второй — ещё на 100px выше. Compose-кнопка действия // второй — ещё на 100px выше. Compose-кнопка действия

View File

@ -378,10 +378,15 @@ void PayByPhoneScript::makePayment(
// отдельного узла "Подтверждаю" в дампе нет — координаты берём OCR-ом // отдельного узла "Подтверждаю" в дампе нет — координаты берём OCR-ом
// по скриншоту. Если OCR не нашёл — fallback на геометрию (центр // по скриншоту. Если OCR не нашёл — fallback на геометрию (центр
// нижней четверти sheet, выше системного nav-бара). // нижней четверти sheet, выше системного nav-бара).
// Compose отдаёт sheet как одну ноду на весь экран ([0,0][720,1640]),
// поэтому confirmNode.y1/y2 не сужают поиск — OCR сканирует весь
// скриншот и ловит ложные совпадения (буква "в" в шапке).
// Ограничиваем нижней половиной — действие-кнопка всегда внизу sheet'a.
const int ocrYMin = (confirmNode.y1() + confirmNode.y2()) / 2;
const QByteArray screen = AdbUtils::captureScreenshotBytes(deviceId); const QByteArray screen = AdbUtils::captureScreenshotBytes(deviceId);
QPoint tap = OcrUtils::findTextCenter(screen, QPoint tap = OcrUtils::findTextCenter(screen,
QString::fromUtf8("Подтверждаю"), QString::fromUtf8("Подтверждаю"),
confirmNode.y1(), confirmNode.y2()); ocrYMin, confirmNode.y2());
if (tap.x() < 0) { if (tap.x() < 0) {
const int tapY = confirmNode.y2() - qMax(150, height / 14); const int tapY = confirmNode.y2() - qMax(150, height / 14);
tap = QPoint(confirmNode.x(), tapY); tap = QPoint(confirmNode.x(), tapY);
@ -470,10 +475,11 @@ void PayByPhoneScript::makePayment(
++confirmRetries; ++confirmRetries;
qDebug() << "[Dushanbe::PayByPhone] Confirm sheet still visible, re-tap" qDebug() << "[Dushanbe::PayByPhone] Confirm sheet still visible, re-tap"
<< confirmRetries << "/" << maxConfirmRetries; << confirmRetries << "/" << maxConfirmRetries;
const int ocrYMin = (retryConfirm.y1() + retryConfirm.y2()) / 2;
const QByteArray screen = AdbUtils::captureScreenshotBytes(deviceId); const QByteArray screen = AdbUtils::captureScreenshotBytes(deviceId);
QPoint tap = OcrUtils::findTextCenter(screen, QPoint tap = OcrUtils::findTextCenter(screen,
QString::fromUtf8("Подтверждаю"), QString::fromUtf8("Подтверждаю"),
retryConfirm.y1(), retryConfirm.y2()); ocrYMin, retryConfirm.y2());
if (tap.x() < 0) { if (tap.x() < 0) {
// Варьируем Y: первый retry — выше fallback на 100px, // Варьируем Y: первый retry — выше fallback на 100px,
// второй — ещё на 100px выше. Compose-кнопка действия // второй — ещё на 100px выше. Compose-кнопка действия

View File

@ -70,7 +70,12 @@ bool fuzzyMatch(const QString &word, const QString &needle) {
if (word.isEmpty() || needle.isEmpty()) return false; if (word.isEmpty() || needle.isEmpty()) return false;
const QString w = word.toLower(); const QString w = word.toLower();
const QString n = needle.toLower(); const QString n = needle.toLower();
if (w.contains(n) || n.contains(w)) return true; // Substring-ветка работает только если word достаточно длинный.
// Иначе любая одиночная буква, входящая в needle (для «подтверждаю»:
// п/о/д/т/в/е/р/ж/а/ю), даст ложный матч на случайный текст экрана —
// ровно так бот тапал в (620,302) по букве «в» где-то в шапке sheet'a.
const int minLen = qMax(qMin(n.size(), 3), n.size() * 3 / 5);
if (w.size() >= minLen && (w.contains(n) || n.contains(w))) return true;
// Допускаем до 20% ошибок (но не меньше 1) — Tesseract на «Подтверждаю» иногда // Допускаем до 20% ошибок (но не меньше 1) — Tesseract на «Подтверждаю» иногда
// даёт «Подтверждак»/«Подтверждак)» из-за оранжевого фона и сглаживания. // даёт «Подтверждак»/«Подтверждак)» из-за оранжевого фона и сглаживания.
const int tolerance = qMax(1, n.size() / 5); const int tolerance = qMax(1, n.size() / 5);