@web-auto/webauto 0.1.17 → 0.1.19
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.
- package/README.md +122 -53
- package/apps/desktop-console/dist/main/index.mjs +229 -14
- package/apps/desktop-console/dist/renderer/index.js +237 -8
- package/apps/desktop-console/entry/ui-cli.mjs +290 -21
- package/apps/desktop-console/entry/ui-console.mjs +46 -15
- package/apps/webauto/entry/account.mjs +126 -27
- package/apps/webauto/entry/lib/account-detect.mjs +399 -9
- package/apps/webauto/entry/lib/account-store.mjs +201 -109
- package/apps/webauto/entry/lib/iflow-reply.mjs +194 -0
- package/apps/webauto/entry/lib/profile-policy.mjs +48 -0
- package/apps/webauto/entry/lib/profilepool.mjs +12 -0
- package/apps/webauto/entry/lib/schedule-store.mjs +29 -2
- package/apps/webauto/entry/lib/session-init.mjs +227 -0
- package/apps/webauto/entry/lib/upgrade-check.mjs +269 -0
- package/apps/webauto/entry/lib/xhs-unified-blocks.mjs +160 -0
- package/apps/webauto/entry/lib/xhs-unified-output-blocks.mjs +83 -0
- package/apps/webauto/entry/lib/xhs-unified-plan-blocks.mjs +55 -0
- package/apps/webauto/entry/lib/xhs-unified-profile-blocks.mjs +542 -0
- package/apps/webauto/entry/lib/xhs-unified-runtime-blocks.mjs +436 -0
- package/apps/webauto/entry/profilepool.mjs +56 -9
- package/apps/webauto/entry/smart-reply-cli.mjs +267 -0
- package/apps/webauto/entry/weibo-unified.mjs +84 -11
- package/apps/webauto/entry/xhs-orchestrate.mjs +43 -1
- package/apps/webauto/entry/xhs-unified.mjs +92 -997
- package/bin/webauto.mjs +22 -4
- package/dist/modules/camo-backend/src/index.js +33 -0
- package/dist/modules/camo-backend/src/internal/BrowserSession.js +232 -49
- package/dist/modules/camo-backend/src/internal/engine-manager.js +14 -13
- package/dist/modules/camo-backend/src/internal/ws-server.js +16 -19
- package/dist/modules/camo-runtime/src/utils/browser-service.mjs +38 -6
- package/dist/modules/workflow/blocks/EnsureSession.js +0 -8
- package/dist/modules/workflow/blocks/WeiboCollectFromLinksBlock.js +78 -6
- package/dist/modules/workflow/blocks/WeiboCollectSearchLinksBlock.js +266 -192
- package/dist/modules/workflow/definitions/weibo-search-workflow-v1.js +2 -0
- package/dist/modules/workflow/src/runner.js +2 -0
- package/dist/modules/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +150 -37
- package/dist/modules/xiaohongshu/app/src/blocks/SmartReplyBlock.js +491 -0
- package/modules/camo-backend/src/index.ts +31 -0
- package/modules/camo-backend/src/internal/BrowserSession.ts +224 -53
- package/modules/camo-backend/src/internal/engine-manager.ts +14 -15
- package/modules/camo-backend/src/internal/ws-server.ts +17 -17
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/common.mjs +12 -2
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/persistence.mjs +57 -0
- package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +2475 -243
- package/modules/camo-runtime/src/autoscript/runtime.mjs +35 -30
- package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +80 -443
- package/modules/camo-runtime/src/container/runtime-core/checkpoint.mjs +39 -6
- package/modules/camo-runtime/src/container/runtime-core/operations/index.mjs +206 -39
- package/modules/camo-runtime/src/container/runtime-core/operations/tab-pool.mjs +0 -79
- package/modules/camo-runtime/src/container/runtime-core/operations/viewport.mjs +46 -0
- package/modules/camo-runtime/src/utils/browser-service.mjs +41 -6
- package/modules/camo-runtime/src/utils/js-policy.mjs +28 -0
- package/modules/workflow/blocks/EnsureSession.ts +0 -4
- package/modules/workflow/blocks/WeiboCollectFromLinksBlock.ts +81 -6
- package/modules/workflow/blocks/WeiboCollectSearchLinksBlock.ts +316 -0
- package/modules/workflow/definitions/weibo-search-workflow-v1.ts +2 -0
- package/modules/workflow/src/runner.ts +2 -0
- package/modules/xiaohongshu/app/src/blocks/ReplyInteractBlock.ts +198 -53
- package/modules/xiaohongshu/app/src/blocks/SmartReplyBlock.ts +706 -0
- package/package.json +2 -2
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/comments.mjs +0 -498
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/detail.mjs +0 -181
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +0 -691
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +0 -388
- package/modules/camo-runtime/src/container/runtime-core/operations/selector-scripts.mjs +0 -135
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Block: ReplyInteract
|
|
2
|
+
* Block: ReplyInteract(回复评论)
|
|
3
3
|
*
|
|
4
4
|
* 职责:
|
|
5
|
-
* 1
|
|
6
|
-
* 2
|
|
7
|
-
* 3
|
|
5
|
+
* 1. 在指定评论上点击"回复"
|
|
6
|
+
* 2. 定位回复输入框并输入内容(系统级键盘)
|
|
7
|
+
* 3. 点击发送按钮或按回车提交回复
|
|
8
|
+
* 4. 截图留证(包含高亮与 DEV 叠加文案)
|
|
8
9
|
*
|
|
9
10
|
* 约束:
|
|
10
11
|
* - 点击必须走坐标点击(mouse:click),禁止 DOM click
|
|
11
|
-
* -
|
|
12
|
+
* - dryRun 模式下不提交(仅输入不发送)
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
import path from 'node:path';
|
|
@@ -32,12 +33,14 @@ export interface ReplyInteractOutput {
|
|
|
32
33
|
success: boolean;
|
|
33
34
|
noteId: string;
|
|
34
35
|
typed: boolean;
|
|
36
|
+
submitted: boolean; // 是否成功提交
|
|
35
37
|
evidence?: {
|
|
36
38
|
screenshot?: string | null;
|
|
37
39
|
};
|
|
38
40
|
debug?: {
|
|
39
41
|
replyButtonRect?: { x: number; y: number; width: number; height: number };
|
|
40
42
|
replyInputRect?: { x: number; y: number; width: number; height: number };
|
|
43
|
+
sendButtonRect?: { x: number; y: number; width: number; height: number };
|
|
41
44
|
};
|
|
42
45
|
error?: string;
|
|
43
46
|
}
|
|
@@ -73,7 +76,7 @@ async function findReplyButtonTarget(
|
|
|
73
76
|
const raw = candidates.find(el => textEq(el, '回复')) || null;
|
|
74
77
|
if (!raw) return { ok: false, reason: 'reply-button-not-found' };
|
|
75
78
|
|
|
76
|
-
const target = raw.closest && (raw.closest('button,a,[role
|
|
79
|
+
const target = raw.closest && (raw.closest('button,a,[role="button"]') || raw) || raw;
|
|
77
80
|
const r = target.getBoundingClientRect();
|
|
78
81
|
if (!r || !r.width || !r.height) return { ok: false, reason: 'reply-rect-empty' };
|
|
79
82
|
|
|
@@ -143,7 +146,7 @@ async function findReplyInputTarget(sessionId: string, apiUrl: string): Promise<
|
|
|
143
146
|
return { ok: true, rect: { x: Math.round(r.left), y: Math.round(r.top), width: Math.round(r.width), height: Math.round(r.height) }, clickPoint: { x: mx, y: my } };
|
|
144
147
|
}
|
|
145
148
|
|
|
146
|
-
const all = Array.from(document.querySelectorAll('textarea, input[type
|
|
149
|
+
const all = Array.from(document.querySelectorAll('textarea, input[type="text"], input:not([type]), [contenteditable="true"], [contenteditable="plaintext-only"]'))
|
|
147
150
|
.filter(isVisible);
|
|
148
151
|
if (!all.length) return { ok: false, reason: 'no-visible-input' };
|
|
149
152
|
|
|
@@ -163,6 +166,60 @@ async function findReplyInputTarget(sessionId: string, apiUrl: string): Promise<
|
|
|
163
166
|
return payload as ClickTarget;
|
|
164
167
|
}
|
|
165
168
|
|
|
169
|
+
/**
|
|
170
|
+
* 查找发送按钮
|
|
171
|
+
*/
|
|
172
|
+
async function findSendButtonTarget(sessionId: string, apiUrl: string): Promise<ClickTarget> {
|
|
173
|
+
const res = await controllerAction(
|
|
174
|
+
'browser:execute',
|
|
175
|
+
{
|
|
176
|
+
profile: sessionId,
|
|
177
|
+
script: `(() => {
|
|
178
|
+
const isVisible = (el) => {
|
|
179
|
+
const r = el.getBoundingClientRect();
|
|
180
|
+
return r.width > 0 && r.height > 0 && r.bottom > 0 && r.top < window.innerHeight;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
// 查找发送按钮 - 多种策略
|
|
184
|
+
const strategies = [
|
|
185
|
+
// 1. 查找包含"发送"文本的按钮
|
|
186
|
+
() => Array.from(document.querySelectorAll('button, a, div, span')).find(el =>
|
|
187
|
+
isVisible(el) && /发送|submit|send/i.test(el.textContent || '')
|
|
188
|
+
),
|
|
189
|
+
// 2. 查找发送图标按钮
|
|
190
|
+
() => document.querySelector('button[class*="send"], button[class*="submit"], [data-type="send"]'),
|
|
191
|
+
// 3. 查找输入框旁边的按钮
|
|
192
|
+
() => {
|
|
193
|
+
const input = document.querySelector('textarea, input[type="text"], [contenteditable="true"]');
|
|
194
|
+
if (!input) return null;
|
|
195
|
+
const parent = input.closest('.comment-form, .reply-form, form, [class*="comment"]');
|
|
196
|
+
if (!parent) return null;
|
|
197
|
+
return parent.querySelector('button, [role="button"]');
|
|
198
|
+
},
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
for (const strategy of strategies) {
|
|
202
|
+
const btn = strategy();
|
|
203
|
+
if (btn && isVisible(btn)) {
|
|
204
|
+
const r = btn.getBoundingClientRect();
|
|
205
|
+
const mx = Math.round((r.left + r.right) / 2);
|
|
206
|
+
const my = Math.round((r.top + r.bottom) / 2);
|
|
207
|
+
return {
|
|
208
|
+
ok: true,
|
|
209
|
+
rect: { x: Math.round(r.left), y: Math.round(r.top), width: Math.round(r.width), height: Math.round(r.height) },
|
|
210
|
+
clickPoint: { x: mx, y: my },
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return { ok: false, reason: 'send-button-not-found' };
|
|
216
|
+
})()`,
|
|
217
|
+
},
|
|
218
|
+
apiUrl,
|
|
219
|
+
);
|
|
220
|
+
return (res?.result || res?.data?.result || res) as ClickTarget;
|
|
221
|
+
}
|
|
222
|
+
|
|
166
223
|
async function drawOverlay(
|
|
167
224
|
sessionId: string,
|
|
168
225
|
apiUrl: string,
|
|
@@ -262,6 +319,81 @@ async function verifyTyped(sessionId: string, apiUrl: string, expected: string):
|
|
|
262
319
|
return Boolean(payload?.contains);
|
|
263
320
|
}
|
|
264
321
|
|
|
322
|
+
async function stillContainsReplyInput(sessionId: string, apiUrl: string, expected: string): Promise<boolean> {
|
|
323
|
+
const normalized = String(expected || '').replace(/\s+/g, ' ').trim();
|
|
324
|
+
const prefix = normalized.slice(0, 8);
|
|
325
|
+
if (!prefix) return false;
|
|
326
|
+
|
|
327
|
+
const res = await controllerAction(
|
|
328
|
+
'browser:execute',
|
|
329
|
+
{
|
|
330
|
+
profile: sessionId,
|
|
331
|
+
script: `(() => {
|
|
332
|
+
const needle = ${JSON.stringify(prefix)};
|
|
333
|
+
const norm = (s) => String(s || '').replace(/\\s+/g, ' ').trim();
|
|
334
|
+
const values = [];
|
|
335
|
+
const nodes = Array.from(document.querySelectorAll('textarea, input[type="text"], input:not([type]), [contenteditable="true"], [contenteditable="plaintext-only"]'));
|
|
336
|
+
for (const node of nodes) {
|
|
337
|
+
if (!node || !node.getBoundingClientRect) continue;
|
|
338
|
+
const r = node.getBoundingClientRect();
|
|
339
|
+
if (!(r.width > 0 && r.height > 0 && r.bottom > 0 && r.top < window.innerHeight)) continue;
|
|
340
|
+
let v = '';
|
|
341
|
+
if (node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement) v = node.value || '';
|
|
342
|
+
else v = node.textContent || '';
|
|
343
|
+
const text = norm(v);
|
|
344
|
+
if (text) values.push(text);
|
|
345
|
+
}
|
|
346
|
+
const contains = values.some((v) => v.includes(needle));
|
|
347
|
+
return { ok: true, contains };
|
|
348
|
+
})()`,
|
|
349
|
+
},
|
|
350
|
+
apiUrl,
|
|
351
|
+
).catch((): null => null);
|
|
352
|
+
|
|
353
|
+
const payload = (res as any)?.result || (res as any)?.data?.result || res;
|
|
354
|
+
return Boolean(payload?.contains);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* 提交回复 - 点击发送按钮或按回车
|
|
359
|
+
*/
|
|
360
|
+
async function submitReply(
|
|
361
|
+
sessionId: string,
|
|
362
|
+
apiUrl: string,
|
|
363
|
+
expectedReplyText: string,
|
|
364
|
+
): Promise<{ ok: boolean; method: 'button' | 'enter' | 'none'; error?: string }> {
|
|
365
|
+
const verifyAfterSubmit = async (method: 'button' | 'enter') => {
|
|
366
|
+
await delay(600);
|
|
367
|
+
const hasPendingInput = await stillContainsReplyInput(sessionId, apiUrl, expectedReplyText);
|
|
368
|
+
if (!hasPendingInput) return { ok: true, method } as const;
|
|
369
|
+
return { ok: false, method, error: 'submit_not_confirmed' } as const;
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
// 首先尝试找发送按钮
|
|
373
|
+
const sendBtn = await findSendButtonTarget(sessionId, apiUrl);
|
|
374
|
+
|
|
375
|
+
if (sendBtn.ok && sendBtn.clickPoint) {
|
|
376
|
+
await controllerAction(
|
|
377
|
+
'mouse:click',
|
|
378
|
+
{ profileId: sessionId, x: Math.round(sendBtn.clickPoint.x), y: Math.round(sendBtn.clickPoint.y) },
|
|
379
|
+
apiUrl,
|
|
380
|
+
);
|
|
381
|
+
const checked = await verifyAfterSubmit('button');
|
|
382
|
+
if (checked.ok) return checked;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// 如果没有找到按钮,尝试按回车
|
|
386
|
+
await controllerAction(
|
|
387
|
+
'keyboard:press',
|
|
388
|
+
{ profileId: sessionId, key: 'Enter' },
|
|
389
|
+
apiUrl,
|
|
390
|
+
);
|
|
391
|
+
const checked = await verifyAfterSubmit('enter');
|
|
392
|
+
if (checked.ok) return checked;
|
|
393
|
+
|
|
394
|
+
return { ok: false, method: 'none', error: checked.error || 'submit_failed' };
|
|
395
|
+
}
|
|
396
|
+
|
|
265
397
|
export async function execute(input: ReplyInteractInput): Promise<ReplyInteractOutput> {
|
|
266
398
|
const {
|
|
267
399
|
sessionId,
|
|
@@ -278,6 +410,7 @@ export async function execute(input: ReplyInteractInput): Promise<ReplyInteractO
|
|
|
278
410
|
let screenshot: string | null = null;
|
|
279
411
|
let replyButtonRect: { x: number; y: number; width: number; height: number } | undefined = undefined;
|
|
280
412
|
let replyInputRect: { x: number; y: number; width: number; height: number } | undefined = undefined;
|
|
413
|
+
let sendButtonRect: { x: number; y: number; width: number; height: number } | undefined = undefined;
|
|
281
414
|
|
|
282
415
|
try {
|
|
283
416
|
const btn = await findReplyButtonTarget(sessionId, unifiedApiUrl, commentVisibleIndex);
|
|
@@ -294,17 +427,13 @@ export async function execute(input: ReplyInteractInput): Promise<ReplyInteractO
|
|
|
294
427
|
});
|
|
295
428
|
await delay(350);
|
|
296
429
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
await delay(700);
|
|
305
|
-
} else {
|
|
306
|
-
await delay(450);
|
|
307
|
-
}
|
|
430
|
+
// ✅ 坐标点击(系统点击)打开回复框
|
|
431
|
+
await controllerAction(
|
|
432
|
+
'mouse:click',
|
|
433
|
+
{ profileId: sessionId, x: Math.round(btn.clickPoint.x), y: Math.round(btn.clickPoint.y) },
|
|
434
|
+
unifiedApiUrl,
|
|
435
|
+
);
|
|
436
|
+
await delay(700);
|
|
308
437
|
|
|
309
438
|
const inputTarget = await findReplyInputTarget(sessionId, unifiedApiUrl);
|
|
310
439
|
if (inputTarget.ok && inputTarget.clickPoint && inputTarget.rect) {
|
|
@@ -318,47 +447,61 @@ export async function execute(input: ReplyInteractInput): Promise<ReplyInteractO
|
|
|
318
447
|
});
|
|
319
448
|
await delay(250);
|
|
320
449
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
await delay(260);
|
|
348
|
-
} else {
|
|
349
|
-
await delay(180);
|
|
350
|
-
}
|
|
450
|
+
// ✅ 坐标点击聚焦输入框
|
|
451
|
+
await controllerAction(
|
|
452
|
+
'mouse:click',
|
|
453
|
+
{ profileId: sessionId, x: Math.round(inputTarget.clickPoint.x), y: Math.round(inputTarget.clickPoint.y) },
|
|
454
|
+
unifiedApiUrl,
|
|
455
|
+
);
|
|
456
|
+
await delay(220);
|
|
457
|
+
|
|
458
|
+
// 清空(可选)
|
|
459
|
+
const isMac = process.platform === 'darwin';
|
|
460
|
+
await controllerAction(
|
|
461
|
+
'keyboard:press',
|
|
462
|
+
{ profileId: sessionId, key: isMac ? 'Meta+A' : 'Control+A' },
|
|
463
|
+
unifiedApiUrl,
|
|
464
|
+
).catch(() => {});
|
|
465
|
+
await delay(80);
|
|
466
|
+
await controllerAction('keyboard:press', { profileId: sessionId, key: 'Backspace' }, unifiedApiUrl).catch(() => {});
|
|
467
|
+
await delay(120);
|
|
468
|
+
|
|
469
|
+
// ✅ 系统级输入
|
|
470
|
+
await controllerAction(
|
|
471
|
+
'keyboard:type',
|
|
472
|
+
{ profileId: sessionId, text: String(replyText || ''), delay: 90, submit: false },
|
|
473
|
+
unifiedApiUrl,
|
|
474
|
+
);
|
|
475
|
+
await delay(260);
|
|
351
476
|
}
|
|
352
477
|
|
|
353
|
-
const typed =
|
|
478
|
+
const typed = replyInputRect
|
|
354
479
|
? await verifyTyped(sessionId, unifiedApiUrl, String(replyText || '').slice(0, 6))
|
|
355
480
|
: false;
|
|
356
481
|
|
|
482
|
+
// ✅ 提交回复(如果不是 dryRun)
|
|
483
|
+
let submitted = false;
|
|
484
|
+
let submitError: string | undefined;
|
|
485
|
+
|
|
486
|
+
if (!dryRun && typed) {
|
|
487
|
+
const submitResult = await submitReply(sessionId, unifiedApiUrl, replyText);
|
|
488
|
+
submitted = submitResult.ok;
|
|
489
|
+
if (!submitResult.ok) {
|
|
490
|
+
submitError = submitResult.error;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// 尝试获取发送按钮位置用于截图
|
|
494
|
+
const sendBtn = await findSendButtonTarget(sessionId, unifiedApiUrl);
|
|
495
|
+
if (sendBtn.ok && sendBtn.rect) {
|
|
496
|
+
sendButtonRect = sendBtn.rect;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
357
500
|
if (dev || dryRun) {
|
|
358
501
|
await drawOverlay(sessionId, unifiedApiUrl, {
|
|
359
502
|
id: 'webauto-dev-reply-label',
|
|
360
503
|
color: '#00ff00',
|
|
361
|
-
label: `[${dryRun ? 'DRYRUN' : '
|
|
504
|
+
label: `[${dryRun ? 'DRYRUN' : submitted ? 'SENT' : 'TYPED'}] note=${noteId} commentIdx=${commentVisibleIndex}\nreply: ${String(replyText || '').slice(0, 80)}`,
|
|
362
505
|
ttlMs: 9000,
|
|
363
506
|
});
|
|
364
507
|
await delay(180);
|
|
@@ -367,7 +510,7 @@ export async function execute(input: ReplyInteractInput): Promise<ReplyInteractO
|
|
|
367
510
|
const base64 = await takeScreenshotBase64(sessionId, unifiedApiUrl);
|
|
368
511
|
if (base64) {
|
|
369
512
|
const outDir = path.join(resolveDownloadRoot(), 'xiaohongshu', env, keyword, 'smart-reply', noteId);
|
|
370
|
-
const name = `reply
|
|
513
|
+
const name = `reply-${dryRun ? 'dryrun' : submitted ? 'sent' : 'typed'}-${String(commentVisibleIndex).padStart(3, '0')}-${Date.now()}.png`;
|
|
371
514
|
screenshot = await savePngBase64(base64, path.join(outDir, name));
|
|
372
515
|
}
|
|
373
516
|
|
|
@@ -375,16 +518,18 @@ export async function execute(input: ReplyInteractInput): Promise<ReplyInteractO
|
|
|
375
518
|
success: true,
|
|
376
519
|
noteId,
|
|
377
520
|
typed,
|
|
521
|
+
submitted: dryRun ? false : submitted,
|
|
378
522
|
evidence: { screenshot },
|
|
379
|
-
debug: { replyButtonRect, replyInputRect },
|
|
523
|
+
debug: { replyButtonRect, replyInputRect, sendButtonRect },
|
|
380
524
|
};
|
|
381
525
|
} catch (e: any) {
|
|
382
526
|
return {
|
|
383
527
|
success: false,
|
|
384
528
|
noteId,
|
|
385
529
|
typed: false,
|
|
530
|
+
submitted: false,
|
|
386
531
|
evidence: { screenshot },
|
|
387
|
-
debug: { replyButtonRect, replyInputRect },
|
|
532
|
+
debug: { replyButtonRect, replyInputRect, sendButtonRect },
|
|
388
533
|
error: e?.message || String(e),
|
|
389
534
|
};
|
|
390
535
|
}
|