@leg3ndy/otto-bridge 0.7.4 → 0.7.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1190,10 +1190,14 @@ export class NativeMacOSJobExecutor {
|
|
|
1190
1190
|
if (!selected) {
|
|
1191
1191
|
throw new Error(`Nao consegui localizar a conversa do WhatsApp com ${action.contact}.`);
|
|
1192
1192
|
}
|
|
1193
|
+
const beforeSend = await this.readWhatsAppVisibleConversation(action.contact, 8).catch(() => ({
|
|
1194
|
+
messages: [],
|
|
1195
|
+
summary: "",
|
|
1196
|
+
}));
|
|
1193
1197
|
await reporter.progress(progressPercent, `Enviando a mensagem para ${action.contact} no WhatsApp`);
|
|
1194
1198
|
await this.sendWhatsAppMessage(action.text);
|
|
1195
1199
|
await delay(900);
|
|
1196
|
-
const verification = await this.
|
|
1200
|
+
const verification = await this.verifyWhatsAppLastMessageAgainstBaseline(action.text, beforeSend.messages);
|
|
1197
1201
|
if (!verification.ok) {
|
|
1198
1202
|
throw new Error(verification.reason || `Nao consegui confirmar o envio da mensagem para ${action.contact} no WhatsApp.`);
|
|
1199
1203
|
}
|
|
@@ -2619,26 +2623,51 @@ return { messages: messages.slice(-maxMessages) };
|
|
|
2619
2623
|
};
|
|
2620
2624
|
}
|
|
2621
2625
|
async verifyWhatsAppLastMessage(expectedText) {
|
|
2626
|
+
return this.verifyWhatsAppLastMessageAgainstBaseline(expectedText);
|
|
2627
|
+
}
|
|
2628
|
+
async verifyWhatsAppLastMessageAgainstBaseline(expectedText, previousMessages) {
|
|
2622
2629
|
const backgroundBrowser = await this.getWhatsAppBackgroundBrowser().catch(() => null);
|
|
2623
2630
|
if (backgroundBrowser) {
|
|
2624
|
-
return backgroundBrowser.verifyLastMessage(expectedText);
|
|
2631
|
+
return backgroundBrowser.verifyLastMessage(expectedText, previousMessages);
|
|
2625
2632
|
}
|
|
2626
|
-
const
|
|
2633
|
+
const baseline = Array.isArray(previousMessages) ? previousMessages : [];
|
|
2634
|
+
const chat = await this.readWhatsAppVisibleConversation("Contato", Math.max(8, baseline.length + 2));
|
|
2627
2635
|
if (!chat.messages.length) {
|
|
2628
2636
|
return {
|
|
2629
2637
|
ok: false,
|
|
2630
2638
|
reason: "Nao consegui ler as mensagens visiveis apos o envio no WhatsApp.",
|
|
2631
2639
|
};
|
|
2632
2640
|
}
|
|
2633
|
-
const normalizedExpected = normalizeText(expectedText).slice(0,
|
|
2634
|
-
const
|
|
2635
|
-
|
|
2641
|
+
const normalizedExpected = normalizeText(expectedText).slice(0, 120);
|
|
2642
|
+
const normalizeMessage = (item) => `${normalizeText(item.author)}|${normalizeText(item.text)}`;
|
|
2643
|
+
const beforeSignature = baseline.map(normalizeMessage).join("\n");
|
|
2644
|
+
const afterSignature = chat.messages.map(normalizeMessage).join("\n");
|
|
2645
|
+
const changed = beforeSignature !== afterSignature;
|
|
2646
|
+
const beforeMatches = baseline.filter((item) => normalizeText(item.text).includes(normalizedExpected)).length;
|
|
2647
|
+
const afterMatches = chat.messages.filter((item) => normalizeText(item.text).includes(normalizedExpected)).length;
|
|
2648
|
+
const latest = chat.messages[chat.messages.length - 1] || null;
|
|
2649
|
+
const latestAuthor = normalizeText(latest?.author || "");
|
|
2650
|
+
const latestText = normalizeText(latest?.text || "");
|
|
2651
|
+
const latestMatches = latestText.includes(normalizedExpected) && (latestAuthor === "voce" || latestAuthor === "você");
|
|
2652
|
+
if ((changed && latestMatches) || (changed && afterMatches > beforeMatches)) {
|
|
2653
|
+
return { ok: true, reason: "" };
|
|
2654
|
+
}
|
|
2655
|
+
if (!changed) {
|
|
2656
|
+
return {
|
|
2657
|
+
ok: false,
|
|
2658
|
+
reason: "O WhatsApp nao mostrou mudanca visivel na conversa depois da tentativa de envio.",
|
|
2659
|
+
};
|
|
2660
|
+
}
|
|
2661
|
+
if (afterMatches <= beforeMatches) {
|
|
2636
2662
|
return {
|
|
2637
2663
|
ok: false,
|
|
2638
|
-
reason: "
|
|
2664
|
+
reason: "A conversa mudou, mas nao apareceu uma nova mensagem com o texto esperado no WhatsApp.",
|
|
2639
2665
|
};
|
|
2640
2666
|
}
|
|
2641
|
-
return {
|
|
2667
|
+
return {
|
|
2668
|
+
ok: false,
|
|
2669
|
+
reason: "Nao consegui confirmar visualmente a nova mensagem enviada no WhatsApp.",
|
|
2670
|
+
};
|
|
2642
2671
|
}
|
|
2643
2672
|
async takeScreenshot(targetPath) {
|
|
2644
2673
|
const artifactsDir = path.join(os.homedir(), ".otto-bridge", "artifacts");
|
|
@@ -125,6 +125,21 @@ export class MacOSWhatsAppHelperRuntime {
|
|
|
125
125
|
await this.start();
|
|
126
126
|
await this.call("hide_background");
|
|
127
127
|
}
|
|
128
|
+
async nativeInsertText(text) {
|
|
129
|
+
await this.start();
|
|
130
|
+
const result = await this.call("native_insert_text", { text });
|
|
131
|
+
return result?.ok === true;
|
|
132
|
+
}
|
|
133
|
+
async nativeClearText() {
|
|
134
|
+
await this.start();
|
|
135
|
+
const result = await this.call("native_clear_text");
|
|
136
|
+
return result?.ok === true;
|
|
137
|
+
}
|
|
138
|
+
async nativePressEnter() {
|
|
139
|
+
await this.start();
|
|
140
|
+
const result = await this.call("native_press_enter");
|
|
141
|
+
return result?.ok === true;
|
|
142
|
+
}
|
|
128
143
|
async evaluate(script) {
|
|
129
144
|
await this.start();
|
|
130
145
|
return await this.call("evaluate_js", { script });
|
|
@@ -287,12 +302,16 @@ export class MacOSWhatsAppHelperRuntime {
|
|
|
287
302
|
if (!(prepared.ok === true)) {
|
|
288
303
|
return false;
|
|
289
304
|
}
|
|
305
|
+
let enterTriggered = false;
|
|
306
|
+
let clickFallbackTriggered = false;
|
|
290
307
|
const deadline = Date.now() + 6_000;
|
|
291
308
|
while (Date.now() < deadline) {
|
|
292
309
|
await delay(500);
|
|
293
310
|
const result = await this.evaluate(`
|
|
294
311
|
(() => {
|
|
295
312
|
const query = ${JSON.stringify(contact)};
|
|
313
|
+
const enterTriggered = ${JSON.stringify(enterTriggered)};
|
|
314
|
+
const clickFallbackTriggered = ${JSON.stringify(clickFallbackTriggered)};
|
|
296
315
|
const normalize = (value) => String(value || "").normalize("NFD").replace(/[\\u0300-\\u036f]/g, "").toLowerCase().trim();
|
|
297
316
|
const normalizedQuery = normalize(query);
|
|
298
317
|
|
|
@@ -305,6 +324,37 @@ export class MacOSWhatsAppHelperRuntime {
|
|
|
305
324
|
return rect.bottom >= 0 && rect.right >= 0 && rect.top <= window.innerHeight && rect.left <= window.innerWidth;
|
|
306
325
|
}
|
|
307
326
|
|
|
327
|
+
function isConversationOpen() {
|
|
328
|
+
const footerVisible = Array.from(document.querySelectorAll('footer, main footer'))
|
|
329
|
+
.some((node) => isVisible(node));
|
|
330
|
+
const composerVisible = Array.from(document.querySelectorAll('[data-testid="conversation-compose-box-input"], footer div[contenteditable="true"], main footer [contenteditable="true"], footer textarea'))
|
|
331
|
+
.some((node) => isVisible(node));
|
|
332
|
+
const headerMatch = Array.from(document.querySelectorAll('header span[title], header div[title], main span[title], main div[title]'))
|
|
333
|
+
.filter((node) => node instanceof HTMLElement)
|
|
334
|
+
.filter((node) => isVisible(node))
|
|
335
|
+
.some((node) => {
|
|
336
|
+
const text = normalize(node.getAttribute("title") || node.textContent || "");
|
|
337
|
+
return text === normalizedQuery || text.includes(normalizedQuery);
|
|
338
|
+
});
|
|
339
|
+
return footerVisible || composerVisible || headerMatch;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (isConversationOpen()) {
|
|
343
|
+
return { clicked: true, activatedBy: "already-open" };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const searchCandidates = Array.from(document.querySelectorAll('div[contenteditable="true"][role="textbox"], input[role="textbox"], textarea, div[contenteditable="true"][data-tab], [data-testid="chat-list-search"] [contenteditable="true"]'))
|
|
347
|
+
.filter((node) => node instanceof HTMLElement)
|
|
348
|
+
.filter((node) => isVisible(node));
|
|
349
|
+
const searchBox = searchCandidates[0];
|
|
350
|
+
if (!enterTriggered && searchBox instanceof HTMLElement) {
|
|
351
|
+
searchBox.focus();
|
|
352
|
+
searchBox.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", code: "Enter", keyCode: 13, which: 13, bubbles: true }));
|
|
353
|
+
searchBox.dispatchEvent(new KeyboardEvent("keypress", { key: "Enter", code: "Enter", keyCode: 13, which: 13, bubbles: true }));
|
|
354
|
+
searchBox.dispatchEvent(new KeyboardEvent("keyup", { key: "Enter", code: "Enter", keyCode: 13, which: 13, bubbles: true }));
|
|
355
|
+
return { clicked: false, enterTriggered: true };
|
|
356
|
+
}
|
|
357
|
+
|
|
308
358
|
const titleNodes = Array.from(document.querySelectorAll('span[title], div[title]'))
|
|
309
359
|
.filter((node) => node instanceof HTMLElement)
|
|
310
360
|
.filter((node) => isVisible(node))
|
|
@@ -325,17 +375,27 @@ export class MacOSWhatsAppHelperRuntime {
|
|
|
325
375
|
return { clicked: false };
|
|
326
376
|
}
|
|
327
377
|
|
|
378
|
+
if (clickFallbackTriggered) {
|
|
379
|
+
return { clicked: false };
|
|
380
|
+
}
|
|
381
|
+
|
|
328
382
|
const winner = titleNodes[0];
|
|
329
383
|
const target = winner.container instanceof HTMLElement ? winner.container : winner.node;
|
|
330
384
|
target.scrollIntoView({ block: "center", inline: "center", behavior: "auto" });
|
|
331
385
|
if (typeof target.click === "function") {
|
|
332
386
|
target.click();
|
|
333
|
-
return { clicked: true };
|
|
387
|
+
return { clicked: false, clickFallbackTriggered: true };
|
|
334
388
|
}
|
|
335
389
|
target.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true, view: window }));
|
|
336
|
-
return { clicked: true };
|
|
390
|
+
return { clicked: false, clickFallbackTriggered: true };
|
|
337
391
|
})()
|
|
338
392
|
`);
|
|
393
|
+
if (result.enterTriggered === true) {
|
|
394
|
+
enterTriggered = true;
|
|
395
|
+
}
|
|
396
|
+
if (result.clickFallbackTriggered === true) {
|
|
397
|
+
clickFallbackTriggered = true;
|
|
398
|
+
}
|
|
339
399
|
if (result.clicked === true) {
|
|
340
400
|
return true;
|
|
341
401
|
}
|
|
@@ -398,50 +458,91 @@ export class MacOSWhatsAppHelperRuntime {
|
|
|
398
458
|
}
|
|
399
459
|
|
|
400
460
|
const composer = candidates[0].node;
|
|
401
|
-
composer.focus();
|
|
402
461
|
if (composer instanceof HTMLInputElement || composer instanceof HTMLTextAreaElement) {
|
|
462
|
+
composer.focus();
|
|
463
|
+
composer.click();
|
|
403
464
|
composer.value = "";
|
|
404
465
|
composer.dispatchEvent(new Event("input", { bubbles: true }));
|
|
405
466
|
composer.value = value;
|
|
406
467
|
composer.dispatchEvent(new Event("input", { bubbles: true }));
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
468
|
+
composer.click();
|
|
469
|
+
|
|
470
|
+
const sendCandidates = Array.from(document.querySelectorAll('[data-testid="compose-btn-send"], button[aria-label*="Send"], button[aria-label*="Enviar"], span[data-icon="send"], div[role="button"][aria-label*="Send"], div[role="button"][aria-label*="Enviar"]'))
|
|
471
|
+
.map((node) => node instanceof HTMLElement ? (node.closest('button, div[role="button"]') || node) : null)
|
|
472
|
+
.filter((node) => node instanceof HTMLElement)
|
|
473
|
+
.filter((node) => isVisible(node));
|
|
474
|
+
|
|
475
|
+
const sendButton = sendCandidates[0];
|
|
476
|
+
if (sendButton instanceof HTMLElement) {
|
|
477
|
+
sendButton.scrollIntoView({ block: "center", inline: "center", behavior: "auto" });
|
|
478
|
+
if (typeof sendButton.click === "function") {
|
|
479
|
+
sendButton.click();
|
|
480
|
+
return { sent: true };
|
|
481
|
+
}
|
|
482
|
+
sendButton.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true, view: window }));
|
|
483
|
+
return { sent: true };
|
|
418
484
|
}
|
|
419
|
-
|
|
485
|
+
|
|
486
|
+
composer.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter", code: "Enter", keyCode: 13, which: 13, bubbles: true }));
|
|
487
|
+
composer.dispatchEvent(new KeyboardEvent("keypress", { key: "Enter", code: "Enter", keyCode: 13, which: 13, bubbles: true }));
|
|
488
|
+
composer.dispatchEvent(new KeyboardEvent("keyup", { key: "Enter", code: "Enter", keyCode: 13, which: 13, bubbles: true }));
|
|
489
|
+
return { sent: true };
|
|
490
|
+
} else {
|
|
491
|
+
composer.focus();
|
|
492
|
+
composer.click();
|
|
493
|
+
return { nativeInputRequired: true };
|
|
494
|
+
}
|
|
495
|
+
})()
|
|
496
|
+
`);
|
|
497
|
+
if (result.nativeInputRequired === true) {
|
|
498
|
+
const cleared = await this.nativeClearText();
|
|
499
|
+
if (!cleared) {
|
|
500
|
+
throw new Error("O helper nativo do WhatsApp nao conseguiu limpar o rascunho da mensagem.");
|
|
501
|
+
}
|
|
502
|
+
const inserted = await this.nativeInsertText(text);
|
|
503
|
+
if (!inserted) {
|
|
504
|
+
throw new Error("O helper nativo do WhatsApp nao conseguiu digitar a mensagem.");
|
|
505
|
+
}
|
|
506
|
+
await delay(250);
|
|
507
|
+
const clickResult = await this.evaluate(`
|
|
508
|
+
(() => {
|
|
509
|
+
function isVisible(element) {
|
|
510
|
+
if (!(element instanceof HTMLElement)) return false;
|
|
511
|
+
const rect = element.getBoundingClientRect();
|
|
512
|
+
if (rect.width < 6 || rect.height < 6) return false;
|
|
513
|
+
const style = window.getComputedStyle(element);
|
|
514
|
+
if (style.visibility === "hidden" || style.display === "none" || Number(style.opacity || "1") === 0) return false;
|
|
515
|
+
return rect.bottom >= 0 && rect.right >= 0 && rect.top <= window.innerHeight && rect.left <= window.innerWidth;
|
|
420
516
|
}
|
|
421
|
-
composer.click();
|
|
422
517
|
|
|
423
|
-
const sendCandidates = Array.from(document.querySelectorAll('[data-testid="compose-btn-send"], button[aria-label*="Send"], button[aria-label*="Enviar"], span[data-icon="send"], div[role="button"][aria-label*="Send"], div[role="button"][aria-label*="Enviar"]'))
|
|
518
|
+
const sendCandidates = Array.from(document.querySelectorAll('[data-testid="compose-btn-send"], button[aria-label*="Send"], button[aria-label*="Enviar"], span[data-icon="send"], div[role="button"][aria-label*="Send"], div[role="button"][aria-label*="Enviar"], footer [data-icon="send-filled"], footer [data-icon="wds-ic-send-filled"]'))
|
|
424
519
|
.map((node) => node instanceof HTMLElement ? (node.closest('button, div[role="button"]') || node) : null)
|
|
425
520
|
.filter((node) => node instanceof HTMLElement)
|
|
426
521
|
.filter((node) => isVisible(node));
|
|
427
522
|
|
|
428
523
|
const sendButton = sendCandidates[0];
|
|
429
|
-
if (sendButton instanceof HTMLElement) {
|
|
430
|
-
|
|
431
|
-
if (typeof sendButton.click === "function") {
|
|
432
|
-
sendButton.click();
|
|
433
|
-
return { sent: true };
|
|
434
|
-
}
|
|
435
|
-
sendButton.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true, view: window }));
|
|
436
|
-
return { sent: true };
|
|
524
|
+
if (!(sendButton instanceof HTMLElement)) {
|
|
525
|
+
return { clicked: false };
|
|
437
526
|
}
|
|
438
527
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
528
|
+
sendButton.scrollIntoView({ block: "center", inline: "center", behavior: "auto" });
|
|
529
|
+
if (typeof sendButton.click === "function") {
|
|
530
|
+
sendButton.click();
|
|
531
|
+
return { clicked: true };
|
|
532
|
+
}
|
|
533
|
+
sendButton.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true, view: window }));
|
|
534
|
+
return { clicked: true };
|
|
443
535
|
})()
|
|
444
|
-
|
|
536
|
+
`);
|
|
537
|
+
if (clickResult.clicked === true) {
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
const submitted = await this.nativePressEnter();
|
|
541
|
+
if (!submitted) {
|
|
542
|
+
throw new Error("O helper nativo do WhatsApp nao conseguiu enviar a mensagem.");
|
|
543
|
+
}
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
445
546
|
if (result.sent === true) {
|
|
446
547
|
return;
|
|
447
548
|
}
|
|
@@ -495,23 +596,45 @@ export class MacOSWhatsAppHelperRuntime {
|
|
|
495
596
|
: "(sem mensagens visiveis na conversa)",
|
|
496
597
|
};
|
|
497
598
|
}
|
|
498
|
-
async verifyLastMessage(expectedText) {
|
|
499
|
-
const
|
|
599
|
+
async verifyLastMessage(expectedText, previousMessages) {
|
|
600
|
+
const baseline = Array.isArray(previousMessages) ? previousMessages : [];
|
|
601
|
+
const chat = await this.readVisibleConversation(Math.max(8, baseline.length + 2));
|
|
500
602
|
if (!chat.messages.length) {
|
|
501
603
|
return {
|
|
502
604
|
ok: false,
|
|
503
605
|
reason: "Nao consegui ler as mensagens visiveis apos o envio no WhatsApp.",
|
|
504
606
|
};
|
|
505
607
|
}
|
|
506
|
-
const normalizedExpected = normalizeText(expectedText).slice(0,
|
|
507
|
-
const
|
|
508
|
-
|
|
608
|
+
const normalizedExpected = normalizeText(expectedText).slice(0, 120);
|
|
609
|
+
const normalizeMessage = (item) => `${normalizeText(item.author)}|${normalizeText(item.text)}`;
|
|
610
|
+
const beforeSignature = baseline.map(normalizeMessage).join("\n");
|
|
611
|
+
const afterSignature = chat.messages.map(normalizeMessage).join("\n");
|
|
612
|
+
const changed = beforeSignature !== afterSignature;
|
|
613
|
+
const beforeMatches = baseline.filter((item) => normalizeText(item.text).includes(normalizedExpected)).length;
|
|
614
|
+
const afterMatches = chat.messages.filter((item) => normalizeText(item.text).includes(normalizedExpected)).length;
|
|
615
|
+
const latest = chat.messages[chat.messages.length - 1] || null;
|
|
616
|
+
const latestAuthor = normalizeText(latest?.author || "");
|
|
617
|
+
const latestText = normalizeText(latest?.text || "");
|
|
618
|
+
const latestMatches = latestText.includes(normalizedExpected) && (latestAuthor === "voce" || latestAuthor === "você");
|
|
619
|
+
if ((changed && latestMatches) || (changed && afterMatches > beforeMatches)) {
|
|
620
|
+
return { ok: true, reason: "" };
|
|
621
|
+
}
|
|
622
|
+
if (!changed) {
|
|
623
|
+
return {
|
|
624
|
+
ok: false,
|
|
625
|
+
reason: "O WhatsApp nao mostrou mudanca visivel na conversa depois da tentativa de envio.",
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
if (afterMatches <= beforeMatches) {
|
|
509
629
|
return {
|
|
510
630
|
ok: false,
|
|
511
|
-
reason: "
|
|
631
|
+
reason: "A conversa mudou, mas nao apareceu uma nova mensagem com o texto esperado no WhatsApp.",
|
|
512
632
|
};
|
|
513
633
|
}
|
|
514
|
-
return {
|
|
634
|
+
return {
|
|
635
|
+
ok: false,
|
|
636
|
+
reason: "Nao consegui confirmar na conversa do WhatsApp se a nova mensagem foi enviada.",
|
|
637
|
+
};
|
|
515
638
|
}
|
|
516
639
|
handleStdout(chunk) {
|
|
517
640
|
this.stdoutBuffer += chunk;
|
|
@@ -90,6 +90,13 @@ final class OttoWhatsAppHelper: NSObject, WKNavigationDelegate {
|
|
|
90
90
|
case "hide_background":
|
|
91
91
|
hideBackground()
|
|
92
92
|
sendResponse(id: id, result: ["background": true])
|
|
93
|
+
case "native_insert_text":
|
|
94
|
+
let text = String(describing: params["text"] ?? "")
|
|
95
|
+
sendResponse(id: id, result: ["ok": nativeInsertText(text)])
|
|
96
|
+
case "native_clear_text":
|
|
97
|
+
sendResponse(id: id, result: ["ok": nativeClearText()])
|
|
98
|
+
case "native_press_enter":
|
|
99
|
+
sendResponse(id: id, result: ["ok": nativePressEnter()])
|
|
93
100
|
case "load_whatsapp":
|
|
94
101
|
ensureWhatsAppLoaded()
|
|
95
102
|
sendResponse(id: id, result: ["url": webView.url?.absoluteString ?? ""])
|
|
@@ -134,6 +141,14 @@ final class OttoWhatsAppHelper: NSObject, WKNavigationDelegate {
|
|
|
134
141
|
app.activate(ignoringOtherApps: true)
|
|
135
142
|
}
|
|
136
143
|
|
|
144
|
+
private func prepareInteractionWindow() {
|
|
145
|
+
window.ignoresMouseEvents = true
|
|
146
|
+
window.alphaValue = 0
|
|
147
|
+
window.setFrame(NSRect(x: -2200, y: 80, width: 1320, height: 920), display: false)
|
|
148
|
+
window.makeKeyAndOrderFront(nil)
|
|
149
|
+
app.activate(ignoringOtherApps: true)
|
|
150
|
+
}
|
|
151
|
+
|
|
137
152
|
private func hideBackground() {
|
|
138
153
|
window.ignoresMouseEvents = true
|
|
139
154
|
window.alphaValue = 0
|
|
@@ -141,6 +156,44 @@ final class OttoWhatsAppHelper: NSObject, WKNavigationDelegate {
|
|
|
141
156
|
window.orderFrontRegardless()
|
|
142
157
|
}
|
|
143
158
|
|
|
159
|
+
private func currentTextInputClient() -> NSTextInputClient? {
|
|
160
|
+
if let client = window.firstResponder as? NSTextInputClient {
|
|
161
|
+
return client
|
|
162
|
+
}
|
|
163
|
+
if let client = webView as? NSTextInputClient {
|
|
164
|
+
return client
|
|
165
|
+
}
|
|
166
|
+
return nil
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private func nativeInsertText(_ text: String) -> Bool {
|
|
170
|
+
prepareInteractionWindow()
|
|
171
|
+
guard let client = currentTextInputClient() else {
|
|
172
|
+
return false
|
|
173
|
+
}
|
|
174
|
+
client.insertText(text, replacementRange: NSRange(location: NSNotFound, length: 0))
|
|
175
|
+
return true
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private func nativeClearText() -> Bool {
|
|
179
|
+
prepareInteractionWindow()
|
|
180
|
+
guard let responder = window.firstResponder else {
|
|
181
|
+
return false
|
|
182
|
+
}
|
|
183
|
+
responder.selectAll(nil)
|
|
184
|
+
responder.doCommand(by: #selector(NSResponder.deleteBackward(_:)))
|
|
185
|
+
return true
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private func nativePressEnter() -> Bool {
|
|
189
|
+
prepareInteractionWindow()
|
|
190
|
+
guard let responder = window.firstResponder else {
|
|
191
|
+
return false
|
|
192
|
+
}
|
|
193
|
+
responder.doCommand(by: #selector(NSResponder.insertNewline(_:)))
|
|
194
|
+
return true
|
|
195
|
+
}
|
|
196
|
+
|
|
144
197
|
private func evaluateJavaScript(_ script: String, completion: @escaping (Any?, Error?) -> Void) {
|
|
145
198
|
webView.evaluateJavaScript(script) { result, error in
|
|
146
199
|
completion(result, error)
|
package/dist/types.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const BRIDGE_CONFIG_VERSION = 1;
|
|
2
|
-
export const BRIDGE_VERSION = "0.7.
|
|
2
|
+
export const BRIDGE_VERSION = "0.7.5";
|
|
3
3
|
export const BRIDGE_PACKAGE_NAME = "@leg3ndy/otto-bridge";
|
|
4
4
|
export const DEFAULT_API_BASE_URL = "http://localhost:8000";
|
|
5
5
|
export const DEFAULT_POLL_INTERVAL_MS = 3000;
|
|
@@ -693,26 +693,48 @@ export class WhatsAppBackgroundBrowser {
|
|
|
693
693
|
: "(sem mensagens visiveis na conversa)",
|
|
694
694
|
};
|
|
695
695
|
}
|
|
696
|
-
async verifyLastMessage(expectedText) {
|
|
696
|
+
async verifyLastMessage(expectedText, previousMessages) {
|
|
697
697
|
if (this.helperRuntime) {
|
|
698
|
-
return await this.helperRuntime.verifyLastMessage(expectedText);
|
|
698
|
+
return await this.helperRuntime.verifyLastMessage(expectedText, previousMessages);
|
|
699
699
|
}
|
|
700
|
-
const
|
|
700
|
+
const baseline = Array.isArray(previousMessages) ? previousMessages : [];
|
|
701
|
+
const chat = await this.readVisibleConversation(Math.max(8, baseline.length + 2));
|
|
701
702
|
if (!chat.messages.length) {
|
|
702
703
|
return {
|
|
703
704
|
ok: false,
|
|
704
705
|
reason: "Nao consegui ler as mensagens visiveis apos o envio no WhatsApp.",
|
|
705
706
|
};
|
|
706
707
|
}
|
|
707
|
-
const normalizedExpected = normalizeText(expectedText).slice(0,
|
|
708
|
-
const
|
|
709
|
-
|
|
708
|
+
const normalizedExpected = normalizeText(expectedText).slice(0, 120);
|
|
709
|
+
const normalizeMessage = (item) => `${normalizeText(item.author)}|${normalizeText(item.text)}`;
|
|
710
|
+
const beforeSignature = baseline.map(normalizeMessage).join("\n");
|
|
711
|
+
const afterSignature = chat.messages.map(normalizeMessage).join("\n");
|
|
712
|
+
const changed = beforeSignature !== afterSignature;
|
|
713
|
+
const beforeMatches = baseline.filter((item) => normalizeText(item.text).includes(normalizedExpected)).length;
|
|
714
|
+
const afterMatches = chat.messages.filter((item) => normalizeText(item.text).includes(normalizedExpected)).length;
|
|
715
|
+
const latest = chat.messages[chat.messages.length - 1] || null;
|
|
716
|
+
const latestAuthor = normalizeText(latest?.author || "");
|
|
717
|
+
const latestText = normalizeText(latest?.text || "");
|
|
718
|
+
const latestMatches = latestText.includes(normalizedExpected) && (latestAuthor === "voce" || latestAuthor === "você");
|
|
719
|
+
if ((changed && latestMatches) || (changed && afterMatches > beforeMatches)) {
|
|
720
|
+
return { ok: true, reason: "" };
|
|
721
|
+
}
|
|
722
|
+
if (!changed) {
|
|
710
723
|
return {
|
|
711
724
|
ok: false,
|
|
712
|
-
reason: "
|
|
725
|
+
reason: "O WhatsApp nao mostrou mudanca visivel na conversa depois da tentativa de envio.",
|
|
713
726
|
};
|
|
714
727
|
}
|
|
715
|
-
|
|
728
|
+
if (afterMatches <= beforeMatches) {
|
|
729
|
+
return {
|
|
730
|
+
ok: false,
|
|
731
|
+
reason: "A conversa mudou, mas nao apareceu uma nova mensagem com o texto esperado no WhatsApp.",
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
return {
|
|
735
|
+
ok: false,
|
|
736
|
+
reason: "Nao consegui confirmar na conversa do WhatsApp se a nova mensagem foi enviada.",
|
|
737
|
+
};
|
|
716
738
|
}
|
|
717
739
|
async ensureWhatsAppPage() {
|
|
718
740
|
const page = this.page;
|