coffeeinabit 0.0.16 → 0.0.18
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/linkedin_automation.js +116 -9
- package/package.json +1 -1
- package/tools/comment_post.js +21 -19
- package/tools/get_messages.js +8 -7
- package/tools/human_mouse.js +187 -0
- package/tools/like_post.js +6 -6
- package/tools/send_connection_request.js +13 -12
- package/tools/send_messages.js +67 -100
package/linkedin_automation.js
CHANGED
|
@@ -15,6 +15,7 @@ import { executeGetDailyLinkedInConnections } from './tools/get_daily_linkedin_c
|
|
|
15
15
|
import { executeGetNewMessages } from './tools/get_new_messages.js';
|
|
16
16
|
import { executeGetLinkedInUpdates } from './tools/get_linkedin_updates.js';
|
|
17
17
|
import { safeGoto } from './tools/navigation.js';
|
|
18
|
+
import { humanLikeAmbientMove, resetHumanMouseState } from './tools/human_mouse.js';
|
|
18
19
|
|
|
19
20
|
const __filename = fileURLToPath(import.meta.url);
|
|
20
21
|
const __dirname = path.dirname(__filename);
|
|
@@ -37,6 +38,17 @@ export class LinkedInAutomation {
|
|
|
37
38
|
this.userEmail = null;
|
|
38
39
|
this.isActionPollingActive = false;
|
|
39
40
|
this.linkedInSessionDir = getContextDirectory();
|
|
41
|
+
this.ambientMouseTimeout = null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
randomBetween(min, max) {
|
|
45
|
+
return min + Math.random() * (max - min);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async waitRandom(min = 350, max = 550) {
|
|
49
|
+
const delay = this.randomBetween(min, max);
|
|
50
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
51
|
+
return delay;
|
|
40
52
|
}
|
|
41
53
|
|
|
42
54
|
getLinkedInSessionPath() {
|
|
@@ -188,6 +200,7 @@ export class LinkedInAutomation {
|
|
|
188
200
|
await this.launchBrowserAndLogin();
|
|
189
201
|
|
|
190
202
|
this.setupPageListeners();
|
|
203
|
+
this.startAmbientMouseMovements();
|
|
191
204
|
|
|
192
205
|
this.isRunning = true;
|
|
193
206
|
this.status = 'running';
|
|
@@ -267,8 +280,7 @@ export class LinkedInAutomation {
|
|
|
267
280
|
console.log('[LinkedInAutomation] Loading credentials...');
|
|
268
281
|
const credentials = await readLinkedInCredentials();
|
|
269
282
|
|
|
270
|
-
|
|
271
|
-
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
283
|
+
await this.waitRandom(8000, 13000);
|
|
272
284
|
|
|
273
285
|
console.log('[LinkedInAutomation] Waiting for login form...');
|
|
274
286
|
await this.page.waitForSelector('#username', { timeout: 10000 });
|
|
@@ -320,7 +332,7 @@ export class LinkedInAutomation {
|
|
|
320
332
|
if (this.currentUrl.includes('/feed')) {
|
|
321
333
|
console.log('[LinkedInAutomation] Login successful! Detected /feed/ URL');
|
|
322
334
|
|
|
323
|
-
await
|
|
335
|
+
await this.waitRandom(1500, 3200);
|
|
324
336
|
|
|
325
337
|
const secondCheck = this.page.url();
|
|
326
338
|
if (secondCheck.includes('/feed')) {
|
|
@@ -378,6 +390,7 @@ export class LinkedInAutomation {
|
|
|
378
390
|
async closeBrowser() {
|
|
379
391
|
try {
|
|
380
392
|
console.log('[LinkedInAutomation] Closing browser...');
|
|
393
|
+
this.stopAmbientMouseMovements();
|
|
381
394
|
|
|
382
395
|
if (this.page) {
|
|
383
396
|
await this.page.close();
|
|
@@ -404,6 +417,7 @@ export class LinkedInAutomation {
|
|
|
404
417
|
|
|
405
418
|
this.stopScreenshotCapture();
|
|
406
419
|
this.stopActionPolling();
|
|
420
|
+
this.stopAmbientMouseMovements();
|
|
407
421
|
|
|
408
422
|
await this.closeBrowser();
|
|
409
423
|
|
|
@@ -716,8 +730,6 @@ export class LinkedInAutomation {
|
|
|
716
730
|
|
|
717
731
|
async pollForTaskResults(taskId, action, maxWaitTime = 4 * 60 * 1000) {
|
|
718
732
|
const startTime = Date.now();
|
|
719
|
-
const pollInterval = 15000;
|
|
720
|
-
|
|
721
733
|
console.log(`[LinkedInAutomation] Starting to poll for task results: ${taskId}`);
|
|
722
734
|
|
|
723
735
|
this.status = 'waiting_for_backend_analysis';
|
|
@@ -759,7 +771,7 @@ export class LinkedInAutomation {
|
|
|
759
771
|
console.error(`[LinkedInAutomation] Error polling task ${taskId}: ${response.status}`);
|
|
760
772
|
}
|
|
761
773
|
|
|
762
|
-
await
|
|
774
|
+
await this.waitRandom(12000, 20000);
|
|
763
775
|
} catch (error) {
|
|
764
776
|
if (error.message && error.message.includes('404')) {
|
|
765
777
|
console.log(`[LinkedInAutomation] Task ${taskId} not found yet, continuing to poll...`);
|
|
@@ -767,7 +779,7 @@ export class LinkedInAutomation {
|
|
|
767
779
|
console.error(`[LinkedInAutomation] Error polling task ${taskId}:`, error.message);
|
|
768
780
|
}
|
|
769
781
|
|
|
770
|
-
await
|
|
782
|
+
await this.waitRandom(12000, 20000);
|
|
771
783
|
}
|
|
772
784
|
}
|
|
773
785
|
|
|
@@ -852,6 +864,8 @@ export class LinkedInAutomation {
|
|
|
852
864
|
|
|
853
865
|
await this.enablePerformanceMode();
|
|
854
866
|
this.page = await this.context.newPage();
|
|
867
|
+
resetHumanMouseState();
|
|
868
|
+
// await this.installCursorHighlight();
|
|
855
869
|
|
|
856
870
|
console.log('[LinkedInAutomation] Checking if user is already authenticated...');
|
|
857
871
|
|
|
@@ -898,6 +912,8 @@ export class LinkedInAutomation {
|
|
|
898
912
|
|
|
899
913
|
await this.enablePerformanceMode();
|
|
900
914
|
this.page = await this.context.newPage();
|
|
915
|
+
resetHumanMouseState();
|
|
916
|
+
// await this.installCursorHighlight();
|
|
901
917
|
|
|
902
918
|
await safeGoto(this.page, currentUrl, {
|
|
903
919
|
waitUntil: 'domcontentloaded',
|
|
@@ -905,6 +921,7 @@ export class LinkedInAutomation {
|
|
|
905
921
|
});
|
|
906
922
|
|
|
907
923
|
this.setupPageListeners();
|
|
924
|
+
this.startAmbientMouseMovements();
|
|
908
925
|
|
|
909
926
|
console.log('[LinkedInAutomation] Browser visibility updated successfully');
|
|
910
927
|
|
|
@@ -927,7 +944,7 @@ export class LinkedInAutomation {
|
|
|
927
944
|
timeout: 30000
|
|
928
945
|
});
|
|
929
946
|
|
|
930
|
-
await
|
|
947
|
+
await this.waitRandom(2200, 4200);
|
|
931
948
|
|
|
932
949
|
let verifyAttempts = 0;
|
|
933
950
|
const maxVerifyAttempts = 3;
|
|
@@ -946,7 +963,7 @@ export class LinkedInAutomation {
|
|
|
946
963
|
return true;
|
|
947
964
|
}
|
|
948
965
|
|
|
949
|
-
await
|
|
966
|
+
await this.waitRandom(2000, 3600);
|
|
950
967
|
verifyAttempts++;
|
|
951
968
|
}
|
|
952
969
|
|
|
@@ -970,6 +987,96 @@ export class LinkedInAutomation {
|
|
|
970
987
|
}
|
|
971
988
|
|
|
972
989
|
|
|
990
|
+
startAmbientMouseMovements() {
|
|
991
|
+
if (this.ambientMouseTimeout || !this.page) {
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
const scheduleMove = async () => {
|
|
996
|
+
if (!this.page || !this.isRunning) {
|
|
997
|
+
this.ambientMouseTimeout = null;
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
try {
|
|
1002
|
+
await humanLikeAmbientMove(this.page);
|
|
1003
|
+
} catch (error) {
|
|
1004
|
+
console.warn('[LinkedInAutomation] Ambient mouse move failed:', error.message);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
const nextDelay = Math.floor(Math.random() * 7000) + 6000;
|
|
1008
|
+
this.ambientMouseTimeout = setTimeout(scheduleMove, nextDelay);
|
|
1009
|
+
};
|
|
1010
|
+
|
|
1011
|
+
const initialDelay = Math.floor(Math.random() * 4000) + 3000;
|
|
1012
|
+
this.ambientMouseTimeout = setTimeout(scheduleMove, initialDelay);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
stopAmbientMouseMovements() {
|
|
1016
|
+
if (this.ambientMouseTimeout) {
|
|
1017
|
+
clearTimeout(this.ambientMouseTimeout);
|
|
1018
|
+
this.ambientMouseTimeout = null;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
async installCursorHighlight() {
|
|
1023
|
+
if (!this.page) return;
|
|
1024
|
+
|
|
1025
|
+
const script = `
|
|
1026
|
+
(() => {
|
|
1027
|
+
if (window.__ciabCursorHighlightInstalled) return;
|
|
1028
|
+
window.__ciabCursorHighlightInstalled = true;
|
|
1029
|
+
|
|
1030
|
+
const ensureHighlight = () => {
|
|
1031
|
+
if (document.querySelector('.ciab-cursor-highlight')) return;
|
|
1032
|
+
|
|
1033
|
+
const existingStyle = document.getElementById('ciab-cursor-style');
|
|
1034
|
+
if (!existingStyle) {
|
|
1035
|
+
const style = document.createElement('style');
|
|
1036
|
+
style.id = 'ciab-cursor-style';
|
|
1037
|
+
style.textContent = '.ciab-cursor-highlight{position:fixed;width:30px;height:30px;border:2px solid rgba(255,40,40,0.85);background:radial-gradient(rgba(255,0,0,0.35),rgba(255,30,30,0));border-radius:50%;pointer-events:none;transform:translate(-50%,-50%);transition:transform 0.06s ease-out,opacity 0.18s;z-index:2147483647;mix-blend-mode:normal;box-shadow:0 0 12px rgba(255,0,0,0.6);}';
|
|
1038
|
+
document.head.appendChild(style);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
const existingCursor = document.querySelector('.ciab-cursor-highlight');
|
|
1042
|
+
const cursor = existingCursor || document.createElement('div');
|
|
1043
|
+
cursor.className = 'ciab-cursor-highlight';
|
|
1044
|
+
cursor.style.opacity = '0';
|
|
1045
|
+
if (!existingCursor) {
|
|
1046
|
+
document.body.appendChild(cursor);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
let lastMove = performance.now();
|
|
1050
|
+
|
|
1051
|
+
const updatePosition = (event) => {
|
|
1052
|
+
cursor.style.opacity = '1';
|
|
1053
|
+
cursor.style.transform = 'translate(' + event.clientX + 'px,' + event.clientY + 'px)';
|
|
1054
|
+
lastMove = performance.now();
|
|
1055
|
+
};
|
|
1056
|
+
|
|
1057
|
+
document.addEventListener('mousemove', updatePosition, { passive: true });
|
|
1058
|
+
|
|
1059
|
+
const fadeLoop = () => {
|
|
1060
|
+
if (performance.now() - lastMove > 1800) {
|
|
1061
|
+
cursor.style.opacity = '0';
|
|
1062
|
+
}
|
|
1063
|
+
requestAnimationFrame(fadeLoop);
|
|
1064
|
+
};
|
|
1065
|
+
fadeLoop();
|
|
1066
|
+
};
|
|
1067
|
+
|
|
1068
|
+
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
|
1069
|
+
ensureHighlight();
|
|
1070
|
+
} else {
|
|
1071
|
+
document.addEventListener('DOMContentLoaded', ensureHighlight, { once: true });
|
|
1072
|
+
}
|
|
1073
|
+
})();
|
|
1074
|
+
`;
|
|
1075
|
+
|
|
1076
|
+
await this.page.addInitScript(script);
|
|
1077
|
+
await this.page.evaluate(script).catch(() => {});
|
|
1078
|
+
}
|
|
1079
|
+
|
|
973
1080
|
getStatus() {
|
|
974
1081
|
return {
|
|
975
1082
|
status: this.status,
|
package/package.json
CHANGED
package/tools/comment_post.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { safeGoto } from './navigation.js';
|
|
2
|
+
import { humanLikeClick, waitRandom, randomBetween } from './human_mouse.js';
|
|
3
|
+
import { humanLikeType } from './human_typing.js';
|
|
2
4
|
|
|
3
5
|
export async function executeCommentPost(page, action) {
|
|
4
6
|
const postUrl = action.parameters?.url || action.url;
|
|
@@ -10,13 +12,13 @@ export async function executeCommentPost(page, action) {
|
|
|
10
12
|
|
|
11
13
|
await safeGoto(page, postUrl, {
|
|
12
14
|
waitUntil: 'domcontentloaded',
|
|
13
|
-
timeout: Math.floor(
|
|
15
|
+
timeout: Math.floor(randomBetween(40000, 60000))
|
|
14
16
|
});
|
|
15
17
|
|
|
16
18
|
await page.waitForSelector('main', { timeout: 10000 });
|
|
17
|
-
await
|
|
19
|
+
await waitRandom(1200, 3200, page);
|
|
18
20
|
await page.evaluate(() => { window.scrollTo(0, document.body.scrollHeight / 2); });
|
|
19
|
-
await
|
|
21
|
+
await waitRandom(900, 2100, page);
|
|
20
22
|
|
|
21
23
|
let commentEditor = await page.locator('.comments-comment-box-comment__text-editor .ql-editor[contenteditable="true"]').first();
|
|
22
24
|
|
|
@@ -31,22 +33,22 @@ export async function executeCommentPost(page, action) {
|
|
|
31
33
|
};
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
await altEditor
|
|
35
|
-
await
|
|
36
|
-
await altEditor
|
|
37
|
-
await
|
|
36
|
+
await humanLikeClick(page, altEditor, { timeout: 5000 });
|
|
37
|
+
await waitRandom(600, 900, page);
|
|
38
|
+
await humanLikeType(altEditor, commentText);
|
|
39
|
+
await waitRandom(600, 900, page);
|
|
38
40
|
|
|
39
41
|
let submitButton = await page.locator('.comments-comment-box__submit-button--cr').first();
|
|
40
42
|
|
|
41
43
|
if (await submitButton.count() > 0) {
|
|
42
|
-
await submitButton
|
|
43
|
-
await
|
|
44
|
+
await humanLikeClick(page, submitButton, { timeout: 5000 });
|
|
45
|
+
await waitRandom(900, 1600, page);
|
|
44
46
|
} else {
|
|
45
47
|
const altSubmitButton = await page.locator('button:has-text("Comment")').first();
|
|
46
48
|
|
|
47
49
|
if (await altSubmitButton.count() > 0) {
|
|
48
|
-
await altSubmitButton
|
|
49
|
-
await
|
|
50
|
+
await humanLikeClick(page, altSubmitButton, { timeout: 5000 });
|
|
51
|
+
await waitRandom(900, 1600, page);
|
|
50
52
|
} else {
|
|
51
53
|
return {
|
|
52
54
|
action: 'comment_post',
|
|
@@ -56,22 +58,22 @@ export async function executeCommentPost(page, action) {
|
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
60
|
} else {
|
|
59
|
-
await commentEditor
|
|
60
|
-
await
|
|
61
|
-
await commentEditor
|
|
62
|
-
await
|
|
61
|
+
await humanLikeClick(page, commentEditor, { timeout: 5000 });
|
|
62
|
+
await waitRandom(600, 900, page);
|
|
63
|
+
await humanLikeType(commentEditor, commentText);
|
|
64
|
+
await waitRandom(600, 900, page);
|
|
63
65
|
|
|
64
66
|
let submitButton = await page.locator('.comments-comment-box__submit-button--cr').first();
|
|
65
67
|
|
|
66
68
|
if (await submitButton.count() > 0) {
|
|
67
|
-
await submitButton
|
|
68
|
-
await
|
|
69
|
+
await humanLikeClick(page, submitButton, { timeout: 5000 });
|
|
70
|
+
await waitRandom(900, 1600, page);
|
|
69
71
|
} else {
|
|
70
72
|
const altSubmitButton = await page.locator('button:has-text("Comment")').first();
|
|
71
73
|
|
|
72
74
|
if (await altSubmitButton.count() > 0) {
|
|
73
|
-
await altSubmitButton
|
|
74
|
-
await
|
|
75
|
+
await humanLikeClick(page, altSubmitButton, { timeout: 5000 });
|
|
76
|
+
await waitRandom(900, 1600, page);
|
|
75
77
|
} else {
|
|
76
78
|
return {
|
|
77
79
|
action: 'comment_post',
|
package/tools/get_messages.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { safeGoto } from './navigation.js';
|
|
2
|
+
import { humanLikeClick, waitRandom, randomBetween } from './human_mouse.js';
|
|
2
3
|
|
|
3
4
|
export async function executeGetMessages(page, action) {
|
|
4
5
|
console.log('[get_messages] Starting executeGetMessages');
|
|
@@ -15,18 +16,18 @@ export async function executeGetMessages(page, action) {
|
|
|
15
16
|
console.log('[get_messages] Navigating to profile:', profileUrl);
|
|
16
17
|
await safeGoto(page, profileUrl, {
|
|
17
18
|
waitUntil: 'load',
|
|
18
|
-
timeout: Math.floor(
|
|
19
|
+
timeout: Math.floor(randomBetween(40000, 60000))
|
|
19
20
|
});
|
|
20
21
|
console.log('[get_messages] Page loaded, URL:', page.url());
|
|
21
22
|
|
|
22
23
|
await page.waitForLoadState('domcontentloaded');
|
|
23
|
-
await
|
|
24
|
-
await
|
|
24
|
+
await waitRandom(900, 1400, page);
|
|
25
|
+
await waitRandom(1600, 2600, page);
|
|
25
26
|
|
|
26
27
|
await page.evaluate(() => { window.scrollTo(0, 0); });
|
|
27
|
-
await
|
|
28
|
+
await waitRandom(400, 650, page);
|
|
28
29
|
await page.evaluate(() => { window.scrollTo(0, 300); });
|
|
29
|
-
await
|
|
30
|
+
await waitRandom(900, 1500, page);
|
|
30
31
|
|
|
31
32
|
console.log('[get_messages] Closing all conversation bubbles');
|
|
32
33
|
await closeAllConversationBubbles(page);
|
|
@@ -39,7 +40,7 @@ export async function executeGetMessages(page, action) {
|
|
|
39
40
|
} catch (error) {
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
await
|
|
43
|
+
await waitRandom(400, 650, page);
|
|
43
44
|
|
|
44
45
|
console.log('[get_messages] Checking for pending connection request');
|
|
45
46
|
const pendingButton = await getButtonByText(page, 'Pending', true);
|
|
@@ -172,7 +173,7 @@ export async function executeGetMessages(page, action) {
|
|
|
172
173
|
console.error('[get_messages] Failed to open conversation - could not find Message button');
|
|
173
174
|
return {
|
|
174
175
|
action: 'get_messages',
|
|
175
|
-
result: { status: '
|
|
176
|
+
result: { status: 'not_found_message_btn', message: `Could not find Message button on ${username}'s profile. Assume no messages or escalate_case_ask_user_to_provide_message_history` },
|
|
176
177
|
status: 'failed'
|
|
177
178
|
};
|
|
178
179
|
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
export const mouseState = {
|
|
2
|
+
x: 0,
|
|
3
|
+
y: 0,
|
|
4
|
+
initialized: false
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const easeInOutSine = (t) => -(Math.cos(Math.PI * t) - 1) / 2;
|
|
8
|
+
|
|
9
|
+
export const randomBetween = (min, max) => min + Math.random() * (max - min);
|
|
10
|
+
|
|
11
|
+
const sleep = async (page, min = 20, max = 60) => {
|
|
12
|
+
const duration = randomBetween(min, max);
|
|
13
|
+
await page.waitForTimeout(duration);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const getViewportSize = async (page) => {
|
|
17
|
+
const viewportGetter = page.viewportSize;
|
|
18
|
+
if (typeof viewportGetter === 'function') {
|
|
19
|
+
const viewport = viewportGetter.call(page);
|
|
20
|
+
if (viewport) {
|
|
21
|
+
return viewport;
|
|
22
|
+
}
|
|
23
|
+
} else if (viewportGetter && viewportGetter.width && viewportGetter.height) {
|
|
24
|
+
return viewportGetter;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
return await page.evaluate(() => ({
|
|
28
|
+
width: window.innerWidth,
|
|
29
|
+
height: window.innerHeight
|
|
30
|
+
}));
|
|
31
|
+
} catch (_) {
|
|
32
|
+
return { width: 1280, height: 720 };
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const resetHumanMouseState = () => {
|
|
37
|
+
mouseState.initialized = false;
|
|
38
|
+
mouseState.x = 0;
|
|
39
|
+
mouseState.y = 0;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const waitRandom = async (min, max, page) => {
|
|
43
|
+
const delay = randomBetween(min, max);
|
|
44
|
+
if (page && page.waitForTimeout) {
|
|
45
|
+
await page.waitForTimeout(delay);
|
|
46
|
+
} else {
|
|
47
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
48
|
+
}
|
|
49
|
+
return delay;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const humanLikeMouseMove = async (page, targetX, targetY, options = {}) => {
|
|
53
|
+
if (!page) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const speedRange = Array.isArray(options.speedRange) ? options.speedRange : [45, 110];
|
|
58
|
+
|
|
59
|
+
if (!mouseState.initialized) {
|
|
60
|
+
const viewport = await getViewportSize(page);
|
|
61
|
+
const originX = randomBetween(viewport.width * 0.25, viewport.width * 0.75);
|
|
62
|
+
const originY = randomBetween(viewport.height * 0.25, viewport.height * 0.75);
|
|
63
|
+
await page.mouse.move(originX, originY, { steps: 12 + Math.floor(Math.random() * 8) });
|
|
64
|
+
await sleep(page, 80, 140);
|
|
65
|
+
mouseState.initialized = true;
|
|
66
|
+
mouseState.x = originX;
|
|
67
|
+
mouseState.y = originY;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const startX = mouseState.x;
|
|
71
|
+
const startY = mouseState.y;
|
|
72
|
+
|
|
73
|
+
const distance = Math.hypot(targetX - startX, targetY - startY);
|
|
74
|
+
const autoSegments = Math.floor(distance / 120) + 6;
|
|
75
|
+
const segments = Math.max(6, Math.min(14, options.segments ?? autoSegments));
|
|
76
|
+
const jitter = options.jitter ?? Math.min(distance * 0.12, 18);
|
|
77
|
+
|
|
78
|
+
for (let i = 1; i <= segments; i++) {
|
|
79
|
+
const progress = i / segments;
|
|
80
|
+
const eased = easeInOutSine(progress);
|
|
81
|
+
const deviation = jitter * 0.6;
|
|
82
|
+
const intermediateX = startX + (targetX - startX) * eased + randomBetween(-deviation, deviation);
|
|
83
|
+
const intermediateY = startY + (targetY - startY) * eased + randomBetween(-deviation, deviation);
|
|
84
|
+
await page.mouse.move(intermediateX, intermediateY, {
|
|
85
|
+
steps: 3 + Math.floor(Math.random() * 4)
|
|
86
|
+
});
|
|
87
|
+
await sleep(page, speedRange[0], speedRange[1]);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const settleX = targetX + randomBetween(-2.2, 2.2);
|
|
91
|
+
const settleY = targetY + randomBetween(-2.2, 2.2);
|
|
92
|
+
await page.mouse.move(settleX, settleY, { steps: 2 + Math.floor(Math.random() * 3) });
|
|
93
|
+
await sleep(page, speedRange[0], speedRange[1]);
|
|
94
|
+
|
|
95
|
+
mouseState.x = settleX;
|
|
96
|
+
mouseState.y = settleY;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const humanLikeClick = async (page, locator, options = {}) => {
|
|
100
|
+
if (!page || !locator) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const timeout = options.timeout ?? 10000;
|
|
105
|
+
const postClickDelay = options.postClickDelay ?? [140, 280];
|
|
106
|
+
const fallbackOptions = options.fallback;
|
|
107
|
+
const button = options.button ?? 'left';
|
|
108
|
+
const hoverDelay = options.hoverDelay ?? [150, 300];
|
|
109
|
+
|
|
110
|
+
await locator.scrollIntoViewIfNeeded();
|
|
111
|
+
await locator.waitFor({ state: 'visible', timeout });
|
|
112
|
+
|
|
113
|
+
const handle = await locator.elementHandle();
|
|
114
|
+
if (!handle) {
|
|
115
|
+
if (fallbackOptions) {
|
|
116
|
+
await locator.click({ timeout, ...fallbackOptions });
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let box = await handle.boundingBox();
|
|
123
|
+
if (!box) {
|
|
124
|
+
await page.waitForTimeout(50);
|
|
125
|
+
box = await handle.boundingBox();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!box) {
|
|
129
|
+
if (fallbackOptions) {
|
|
130
|
+
await locator.click({ timeout, ...fallbackOptions });
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
await locator.click({ timeout });
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const paddingX = Math.min(box.width * 0.3, 12);
|
|
138
|
+
const paddingY = Math.min(box.height * 0.3, 12);
|
|
139
|
+
|
|
140
|
+
const targetX = randomBetween(box.x + paddingX, box.x + box.width - paddingX);
|
|
141
|
+
const targetY = randomBetween(box.y + paddingY, box.y + box.height - paddingY);
|
|
142
|
+
|
|
143
|
+
await humanLikeMouseMove(page, targetX, targetY, {
|
|
144
|
+
jitter: Math.min(box.width, box.height) * 0.15,
|
|
145
|
+
segments: 5 + Math.floor(Math.random() * 4)
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
for (let i = 0; i < 2; i++) {
|
|
149
|
+
const jitterX = targetX + randomBetween(-2.5, 2.5);
|
|
150
|
+
const jitterY = targetY + randomBetween(-2.5, 2.5);
|
|
151
|
+
await page.mouse.move(jitterX, jitterY, { steps: 2 + Math.floor(Math.random() * 3) });
|
|
152
|
+
await sleep(page, 15, 45);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
await sleep(page, hoverDelay[0], hoverDelay[1]);
|
|
156
|
+
await page.mouse.down({ button });
|
|
157
|
+
await sleep(page, 60, 180);
|
|
158
|
+
await page.mouse.up({ button });
|
|
159
|
+
|
|
160
|
+
const delayRange = Array.isArray(postClickDelay) ? postClickDelay : [postClickDelay, postClickDelay];
|
|
161
|
+
await sleep(page, delayRange[0], delayRange[1]);
|
|
162
|
+
|
|
163
|
+
return true;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export const humanLikeAmbientMove = async (page) => {
|
|
167
|
+
if (!page) return;
|
|
168
|
+
|
|
169
|
+
let viewport = page.viewportSize();
|
|
170
|
+
if (!viewport) {
|
|
171
|
+
viewport = await page.evaluate(() => ({
|
|
172
|
+
width: window.innerWidth,
|
|
173
|
+
height: window.innerHeight
|
|
174
|
+
})).catch(() => ({ width: 1280, height: 720 }));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const safeMargin = 80;
|
|
178
|
+
const targetX = randomBetween(safeMargin, Math.max(safeMargin + 10, viewport.width - safeMargin));
|
|
179
|
+
const targetY = randomBetween(safeMargin, Math.max(safeMargin + 10, viewport.height - safeMargin));
|
|
180
|
+
|
|
181
|
+
await humanLikeMouseMove(page, targetX, targetY, {
|
|
182
|
+
jitter: 12,
|
|
183
|
+
segments: 4 + Math.floor(Math.random() * 4)
|
|
184
|
+
});
|
|
185
|
+
await sleep(page, 40, 120);
|
|
186
|
+
};
|
|
187
|
+
|
package/tools/like_post.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { safeGoto } from './navigation.js';
|
|
2
|
+
import { humanLikeClick, waitRandom, randomBetween } from './human_mouse.js';
|
|
2
3
|
|
|
3
4
|
export async function executeLikePost(page, action) {
|
|
4
5
|
const postUrl = action.parameters?.url || action.url;
|
|
@@ -12,8 +13,9 @@ export async function executeLikePost(page, action) {
|
|
|
12
13
|
timeout: 60000
|
|
13
14
|
});
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
await
|
|
16
|
+
const mainTimeout = Math.floor(randomBetween(5000, 9000));
|
|
17
|
+
await page.waitForSelector('main', { timeout: mainTimeout });
|
|
18
|
+
await waitRandom(600, 3200, page);
|
|
17
19
|
|
|
18
20
|
let likeButton = await page.locator('main button[aria-label="React Like"], main button[aria-label="Unreact Like"]').first();
|
|
19
21
|
|
|
@@ -31,11 +33,9 @@ export async function executeLikePost(page, action) {
|
|
|
31
33
|
status: 'success'
|
|
32
34
|
};
|
|
33
35
|
} else if (ariaLabel === 'React Like') {
|
|
34
|
-
await page
|
|
35
|
-
if (button) button.click();
|
|
36
|
-
}, await likeButton.elementHandle());
|
|
36
|
+
await humanLikeClick(page, likeButton);
|
|
37
37
|
|
|
38
|
-
await
|
|
38
|
+
await waitRandom(700, 2400, page);
|
|
39
39
|
|
|
40
40
|
return {
|
|
41
41
|
action: 'like_post',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { safeGoto } from './navigation.js';
|
|
2
|
+
import { humanLikeClick, waitRandom } from './human_mouse.js';
|
|
2
3
|
|
|
3
4
|
export async function executeSendConnectionRequest(page, action) {
|
|
4
5
|
let username = action.parameters?.username;
|
|
@@ -14,10 +15,10 @@ export async function executeSendConnectionRequest(page, action) {
|
|
|
14
15
|
});
|
|
15
16
|
|
|
16
17
|
await page.waitForLoadState('load');
|
|
17
|
-
await
|
|
18
|
+
await waitRandom(1700, 2600, page);
|
|
18
19
|
|
|
19
20
|
await page.evaluate(() => { window.scrollTo(0, 300); });
|
|
20
|
-
await
|
|
21
|
+
await waitRandom(600, 2500, page);
|
|
21
22
|
|
|
22
23
|
return await detectConnectionStatus(page, username);
|
|
23
24
|
|
|
@@ -40,7 +41,7 @@ async function detectConnectionStatus(page, username) {
|
|
|
40
41
|
moreActionsSelectors = await getButtonByText(page, 'More actions', inMain);
|
|
41
42
|
}
|
|
42
43
|
if (moreActionsSelectors && (await moreActionsSelectors.isVisible())) {
|
|
43
|
-
await clickWithRetries(moreActionsSelectors);
|
|
44
|
+
await clickWithRetries(page, moreActionsSelectors);
|
|
44
45
|
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 1000) + 500));
|
|
45
46
|
}
|
|
46
47
|
let removeConnectionButton = await getButtonByText(page, 'Remove Connection', inMain);
|
|
@@ -83,10 +84,10 @@ async function handleConnectButtonClick(page, username, inMain = true, handleSen
|
|
|
83
84
|
|
|
84
85
|
if (connectButton && (await connectButton.isVisible())) {
|
|
85
86
|
try {
|
|
86
|
-
await clickWithRetries(connectButton);
|
|
87
|
+
await clickWithRetries(page, connectButton);
|
|
87
88
|
|
|
88
89
|
if (handleSendWithoutNote) {
|
|
89
|
-
await
|
|
90
|
+
await waitRandom(1700, 2600, page);
|
|
90
91
|
|
|
91
92
|
let sendWithoutNoteButton = await getButtonByText(page, 'Send without a note', false);
|
|
92
93
|
if (!sendWithoutNoteButton) {
|
|
@@ -98,8 +99,8 @@ async function handleConnectButtonClick(page, username, inMain = true, handleSen
|
|
|
98
99
|
|
|
99
100
|
if (sendWithoutNoteButton && (await sendWithoutNoteButton.isVisible())) {
|
|
100
101
|
try {
|
|
101
|
-
await clickWithRetries(sendWithoutNoteButton);
|
|
102
|
-
await
|
|
102
|
+
await clickWithRetries(page, sendWithoutNoteButton);
|
|
103
|
+
await waitRandom(1700, 2600, page);
|
|
103
104
|
return { status: 'success', message: `Connection request sent successfully to ${username}` };
|
|
104
105
|
} catch (error) {
|
|
105
106
|
return { status: 'unclear', message: `Error clicking Send without a note button for ${username}` };
|
|
@@ -113,7 +114,7 @@ async function handleConnectButtonClick(page, username, inMain = true, handleSen
|
|
|
113
114
|
}
|
|
114
115
|
}
|
|
115
116
|
} else {
|
|
116
|
-
await
|
|
117
|
+
await waitRandom(4200, 6200, page);
|
|
117
118
|
return { status: 'success', message: `Connection request sent successfully to ${username}` };
|
|
118
119
|
}
|
|
119
120
|
} catch (error) {
|
|
@@ -164,17 +165,17 @@ async function getButtonByText(page, text, inMain = false) {
|
|
|
164
165
|
return null;
|
|
165
166
|
}
|
|
166
167
|
|
|
167
|
-
async function clickWithRetries(locator, retries = 3) {
|
|
168
|
+
async function clickWithRetries(page, locator, retries = 3) {
|
|
168
169
|
for (let attempt = 0; attempt < retries; attempt++) {
|
|
169
170
|
try {
|
|
170
171
|
await locator.scrollIntoViewIfNeeded();
|
|
171
172
|
await locator.waitFor({ state: 'visible', timeout: 5000 });
|
|
172
|
-
await
|
|
173
|
+
await humanLikeClick(page, locator);
|
|
173
174
|
return true;
|
|
174
175
|
} catch (_) {
|
|
175
|
-
await
|
|
176
|
+
await waitRandom(700 * (attempt + 1), 1200 * (attempt + 1), page);
|
|
176
177
|
}
|
|
177
178
|
}
|
|
178
|
-
await locator
|
|
179
|
+
await humanLikeClick(page, locator);
|
|
179
180
|
return true;
|
|
180
181
|
}
|
package/tools/send_messages.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { humanLikeType } from './human_typing.js';
|
|
2
2
|
import { safeGoto } from './navigation.js';
|
|
3
|
+
import { humanLikeClick, waitRandom, randomBetween } from './human_mouse.js';
|
|
3
4
|
|
|
4
5
|
export async function executeSendMessages(page, action) {
|
|
5
6
|
console.log('[send_messages] Starting executeSendMessages');
|
|
@@ -21,14 +22,14 @@ export async function executeSendMessages(page, action) {
|
|
|
21
22
|
console.log('[send_messages] Navigating to profile:', profileUrl);
|
|
22
23
|
await safeGoto(page, profileUrl, {
|
|
23
24
|
waitUntil: 'load',
|
|
24
|
-
timeout: Math.floor(
|
|
25
|
+
timeout: Math.floor(randomBetween(40000, 60000))
|
|
25
26
|
});
|
|
26
27
|
console.log('[send_messages] Page loaded, URL:', page.url());
|
|
27
28
|
|
|
28
29
|
await page.waitForLoadState('domcontentloaded');
|
|
29
|
-
await
|
|
30
|
+
await waitRandom(800, 1400, page);
|
|
30
31
|
await page.evaluate(() => { window.scrollTo(0, 300); });
|
|
31
|
-
await
|
|
32
|
+
await waitRandom(600, 2500, page);
|
|
32
33
|
|
|
33
34
|
console.log('[send_messages] Closing all conversation bubbles');
|
|
34
35
|
await closeAllConversationBubbles(page);
|
|
@@ -54,7 +55,7 @@ export async function executeSendMessages(page, action) {
|
|
|
54
55
|
} catch (e) {
|
|
55
56
|
console.log('[send_messages] Error checking elements:', e.message);
|
|
56
57
|
}
|
|
57
|
-
await
|
|
58
|
+
await waitRandom(400, 650, page);
|
|
58
59
|
}
|
|
59
60
|
console.log('[send_messages] Conversation not opened after 10 attempts');
|
|
60
61
|
return false;
|
|
@@ -68,9 +69,9 @@ export async function executeSendMessages(page, action) {
|
|
|
68
69
|
if (await btn.isVisible().catch(() => false)) {
|
|
69
70
|
console.log('[send_messages] Found Message button, clicking...');
|
|
70
71
|
await btn.scrollIntoViewIfNeeded();
|
|
71
|
-
await
|
|
72
|
-
await btn
|
|
73
|
-
await
|
|
72
|
+
await waitRandom(260, 420, page);
|
|
73
|
+
await humanLikeClick(page, btn, { timeout: 5000 });
|
|
74
|
+
await waitRandom(650, 950, page);
|
|
74
75
|
return await checkConversationOpened();
|
|
75
76
|
} else {
|
|
76
77
|
console.log('[send_messages] Message button not visible');
|
|
@@ -87,9 +88,9 @@ export async function executeSendMessages(page, action) {
|
|
|
87
88
|
if (await btn.isVisible().catch(() => false)) {
|
|
88
89
|
console.log('[send_messages] Found send-privately button, clicking...');
|
|
89
90
|
await btn.scrollIntoViewIfNeeded();
|
|
90
|
-
await
|
|
91
|
-
await btn
|
|
92
|
-
await
|
|
91
|
+
await waitRandom(260, 420, page);
|
|
92
|
+
await humanLikeClick(page, btn, { timeout: 5000 });
|
|
93
|
+
await waitRandom(650, 950, page);
|
|
93
94
|
return await checkConversationOpened();
|
|
94
95
|
} else {
|
|
95
96
|
console.log('[send_messages] send-privately button not visible');
|
|
@@ -110,9 +111,9 @@ export async function executeSendMessages(page, action) {
|
|
|
110
111
|
if (!v) continue;
|
|
111
112
|
console.log('[send_messages] Clicking candidate button');
|
|
112
113
|
await btn.scrollIntoViewIfNeeded();
|
|
113
|
-
await
|
|
114
|
-
await btn
|
|
115
|
-
await
|
|
114
|
+
await waitRandom(180, 320, page);
|
|
115
|
+
await humanLikeClick(page, btn, { timeout: 5000 });
|
|
116
|
+
await waitRandom(650, 950, page);
|
|
116
117
|
if (await checkConversationOpened()) return true;
|
|
117
118
|
}
|
|
118
119
|
} catch (e) {
|
|
@@ -130,9 +131,9 @@ export async function executeSendMessages(page, action) {
|
|
|
130
131
|
if (!isVisible) continue;
|
|
131
132
|
console.log('[send_messages] Clicking Message button');
|
|
132
133
|
await btn.scrollIntoViewIfNeeded();
|
|
133
|
-
await
|
|
134
|
-
await btn
|
|
135
|
-
await
|
|
134
|
+
await waitRandom(260, 420, page);
|
|
135
|
+
await humanLikeClick(page, btn, { timeout: 5000 });
|
|
136
|
+
await waitRandom(650, 950, page);
|
|
136
137
|
if (await checkConversationOpened()) return true;
|
|
137
138
|
}
|
|
138
139
|
} catch (e) {
|
|
@@ -156,7 +157,7 @@ export async function executeSendMessages(page, action) {
|
|
|
156
157
|
console.error('[send_messages] Failed to open conversation - could not find Message button');
|
|
157
158
|
return {
|
|
158
159
|
action: 'send_messages',
|
|
159
|
-
result: { status: '
|
|
160
|
+
result: { status: 'not_found_message_btn', message: `Could not find Message button on ${username}'s profile. Assume no messages or escalate_case_ask_user_to_provide_message_history` },
|
|
160
161
|
status: 'failed'
|
|
161
162
|
};
|
|
162
163
|
}
|
|
@@ -169,8 +170,8 @@ export async function executeSendMessages(page, action) {
|
|
|
169
170
|
const message = messages[msgIndex];
|
|
170
171
|
console.log('[send_messages] Processing message', msgIndex + 1, 'of', messages.length, ':', message);
|
|
171
172
|
|
|
172
|
-
await
|
|
173
|
-
await
|
|
173
|
+
await waitRandom(520, 650, page);
|
|
174
|
+
await waitRandom(180, 260, page);
|
|
174
175
|
|
|
175
176
|
const editor = page.locator('[aria-label="Write a message…"]').first();
|
|
176
177
|
console.log('[send_messages] Looking for message editor by aria-label');
|
|
@@ -183,8 +184,8 @@ export async function executeSendMessages(page, action) {
|
|
|
183
184
|
console.log('[send_messages] Editor visible:', isVisible);
|
|
184
185
|
if (isVisible) {
|
|
185
186
|
console.log('[send_messages] Clicking editor');
|
|
186
|
-
await editor
|
|
187
|
-
await
|
|
187
|
+
await humanLikeClick(page, editor, { timeout: 3000, fallback: { force: true, timeout: 3000 } });
|
|
188
|
+
await waitRandom(180, 260, page);
|
|
188
189
|
|
|
189
190
|
console.log('[send_messages] Clearing and focusing editor');
|
|
190
191
|
await editor.evaluate((el) => {
|
|
@@ -193,7 +194,7 @@ export async function executeSendMessages(page, action) {
|
|
|
193
194
|
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
194
195
|
});
|
|
195
196
|
|
|
196
|
-
await
|
|
197
|
+
await waitRandom(130, 210, page);
|
|
197
198
|
|
|
198
199
|
const isFocused = await editor.evaluate((el) => {
|
|
199
200
|
return document.activeElement === el;
|
|
@@ -210,7 +211,7 @@ export async function executeSendMessages(page, action) {
|
|
|
210
211
|
console.log('[send_messages] Error preparing editor:', e.message);
|
|
211
212
|
}
|
|
212
213
|
|
|
213
|
-
await
|
|
214
|
+
await waitRandom(260, 420, page);
|
|
214
215
|
console.log('[send_messages] Retrying to find message input');
|
|
215
216
|
await findAndClickMessageInput(page);
|
|
216
217
|
}
|
|
@@ -218,12 +219,12 @@ export async function executeSendMessages(page, action) {
|
|
|
218
219
|
if (!editorReady) {
|
|
219
220
|
console.warn('[send_messages] Editor not ready after 5 attempts, trying one more time');
|
|
220
221
|
await findAndClickMessageInput(page);
|
|
221
|
-
await
|
|
222
|
+
await waitRandom(420, 620, page);
|
|
222
223
|
}
|
|
223
224
|
|
|
224
225
|
console.log('[send_messages] Typing message:', message);
|
|
225
226
|
await humanLikeType(editor, message);
|
|
226
|
-
await
|
|
227
|
+
await waitRandom(320, 620, page);
|
|
227
228
|
|
|
228
229
|
const textTyped = await editor.evaluate((el) => {
|
|
229
230
|
const text = el.innerText || el.textContent || '';
|
|
@@ -233,16 +234,16 @@ export async function executeSendMessages(page, action) {
|
|
|
233
234
|
|
|
234
235
|
if (!textTyped) {
|
|
235
236
|
console.warn('[send_messages] Text not typed, retrying...');
|
|
236
|
-
await
|
|
237
|
-
await editor
|
|
237
|
+
await waitRandom(420, 620, page);
|
|
238
|
+
await humanLikeClick(page, editor, { timeout: 3000 });
|
|
238
239
|
await editor.evaluate((el) => {
|
|
239
240
|
el.focus();
|
|
240
241
|
el.dispatchEvent(new Event('focus', { bubbles: true }));
|
|
241
242
|
});
|
|
242
|
-
await
|
|
243
|
+
await waitRandom(180, 260, page);
|
|
243
244
|
console.log('[send_messages] Retrying to type message');
|
|
244
245
|
await humanLikeType(editor, message);
|
|
245
|
-
await
|
|
246
|
+
await waitRandom(320, 620, page);
|
|
246
247
|
|
|
247
248
|
const textTypedRetry = await editor.evaluate((el) => {
|
|
248
249
|
const text = el.innerText || el.textContent || '';
|
|
@@ -251,7 +252,7 @@ export async function executeSendMessages(page, action) {
|
|
|
251
252
|
console.log('[send_messages] Text typed after retry:', textTypedRetry);
|
|
252
253
|
}
|
|
253
254
|
|
|
254
|
-
await
|
|
255
|
+
await waitRandom(220, 420, page);
|
|
255
256
|
|
|
256
257
|
console.log('[send_messages] Clicking send button');
|
|
257
258
|
const clickedSend = await clickSendButton(page);
|
|
@@ -265,7 +266,7 @@ export async function executeSendMessages(page, action) {
|
|
|
265
266
|
}
|
|
266
267
|
console.log('[send_messages] Send button clicked successfully');
|
|
267
268
|
|
|
268
|
-
await
|
|
269
|
+
await waitRandom(520, 1500, page);
|
|
269
270
|
}
|
|
270
271
|
|
|
271
272
|
console.log('[send_messages] Closing all conversation bubbles');
|
|
@@ -282,102 +283,68 @@ export async function executeSendMessages(page, action) {
|
|
|
282
283
|
async function typeLikeHuman(page, text) {
|
|
283
284
|
for (const char of text) {
|
|
284
285
|
await page.keyboard.type(char);
|
|
285
|
-
await
|
|
286
|
+
await waitRandom(60, 160);
|
|
286
287
|
}
|
|
287
288
|
}
|
|
288
289
|
|
|
289
290
|
async function findAndClickMessageInput(page) {
|
|
290
291
|
console.log('[send_messages] findAndClickMessageInput: Waiting 1 second');
|
|
291
|
-
await
|
|
292
|
-
|
|
292
|
+
await waitRandom(900, 1200, page);
|
|
293
|
+
|
|
293
294
|
console.log('[send_messages] findAndClickMessageInput: Looking for editor by aria-label');
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
editor.focus();
|
|
299
|
-
editor.click();
|
|
300
|
-
editor.dispatchEvent(new Event('focus', { bubbles: true }));
|
|
301
|
-
return true;
|
|
302
|
-
}
|
|
303
|
-
console.log('[send_messages] Editor not found');
|
|
304
|
-
return false;
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
if (editorClicked) {
|
|
308
|
-
console.log('[send_messages] Editor clicked successfully');
|
|
309
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
295
|
+
const editorLocator = page.locator('[aria-label="Write a message…"]').first();
|
|
296
|
+
if (await editorLocator.isVisible().catch(() => false)) {
|
|
297
|
+
await humanLikeClick(page, editorLocator, { timeout: 3000 });
|
|
298
|
+
await waitRandom(420, 620, page);
|
|
310
299
|
return;
|
|
311
300
|
}
|
|
312
|
-
|
|
301
|
+
|
|
313
302
|
console.log('[send_messages] Editor not found, trying placeholder');
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
editor.focus();
|
|
322
|
-
editor.dispatchEvent(new Event('focus', { bubbles: true }));
|
|
323
|
-
}
|
|
324
|
-
return true;
|
|
303
|
+
const placeholderLocator = page.locator('.msg-form__placeholder').first();
|
|
304
|
+
if (await placeholderLocator.isVisible().catch(() => false)) {
|
|
305
|
+
await humanLikeClick(page, placeholderLocator, { timeout: 3000 });
|
|
306
|
+
await waitRandom(420, 620, page);
|
|
307
|
+
if (await editorLocator.isVisible().catch(() => false)) {
|
|
308
|
+
await humanLikeClick(page, editorLocator, { timeout: 3000 });
|
|
309
|
+
await waitRandom(420, 620, page);
|
|
325
310
|
}
|
|
326
|
-
|
|
327
|
-
return false;
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
if (placeholderClicked) {
|
|
331
|
-
console.log('[send_messages] Placeholder clicked successfully');
|
|
332
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
333
|
-
} else {
|
|
334
|
-
console.warn('[send_messages] Neither editor nor placeholder found');
|
|
311
|
+
return;
|
|
335
312
|
}
|
|
313
|
+
|
|
314
|
+
console.warn('[send_messages] Neither editor nor placeholder found');
|
|
336
315
|
}
|
|
337
316
|
|
|
338
317
|
async function closeAllConversationBubbles(page) {
|
|
339
318
|
console.log('[send_messages] Closing all conversation bubbles');
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
const closeButtons = Array.from(allButtons).filter(btn => {
|
|
343
|
-
const useElement = btn.querySelector('svg use[href="#close-small"]');
|
|
344
|
-
return useElement !== null;
|
|
345
|
-
});
|
|
346
|
-
console.log('[send_messages] Found', closeButtons.length, 'close buttons');
|
|
347
|
-
closeButtons.forEach(btn => {
|
|
348
|
-
try {
|
|
349
|
-
btn.click();
|
|
350
|
-
} catch (e) {
|
|
351
|
-
console.log('[send_messages] Error clicking close button:', e.message);
|
|
352
|
-
}
|
|
353
|
-
});
|
|
319
|
+
const closeButtons = page.locator('button').filter({
|
|
320
|
+
has: page.locator('svg use[href="#close-small"]')
|
|
354
321
|
});
|
|
355
|
-
|
|
322
|
+
const count = await closeButtons.count();
|
|
323
|
+
for (let i = 0; i < count; i++) {
|
|
324
|
+
const btn = closeButtons.nth(i);
|
|
325
|
+
if (await btn.isVisible().catch(() => false)) {
|
|
326
|
+
await humanLikeClick(page, btn, { timeout: 2000 });
|
|
327
|
+
await waitRandom(220, 420, page);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
await waitRandom(420, 620, page);
|
|
356
331
|
}
|
|
357
332
|
|
|
358
333
|
async function clickSendButton(page) {
|
|
359
334
|
console.log('[send_messages] clickSendButton: Waiting 500ms');
|
|
360
|
-
await
|
|
335
|
+
await waitRandom(420, 620, page);
|
|
361
336
|
|
|
362
337
|
for (let attempt = 0; attempt < 10; attempt++) {
|
|
363
338
|
console.log('[send_messages] clickSendButton: Attempt', attempt + 1, 'of 10');
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
sendButton.click();
|
|
369
|
-
return true;
|
|
370
|
-
}
|
|
371
|
-
console.log('[send_messages] Send button not found or disabled');
|
|
372
|
-
return false;
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
if (clicked) {
|
|
339
|
+
const button = page.locator('.msg-form__send-button:not([disabled])').first();
|
|
340
|
+
const isVisible = await button.isVisible().catch(() => false);
|
|
341
|
+
if (isVisible) {
|
|
342
|
+
await humanLikeClick(page, button, { timeout: 3000 });
|
|
376
343
|
console.log('[send_messages] Send button clicked successfully');
|
|
377
344
|
return true;
|
|
378
345
|
}
|
|
379
346
|
|
|
380
|
-
await
|
|
347
|
+
await waitRandom(260, 420, page);
|
|
381
348
|
}
|
|
382
349
|
|
|
383
350
|
console.error('[send_messages] Failed to click send button after 10 attempts');
|