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.
@@ -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 new Promise(resolve => setTimeout(resolve, 2000));
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 new Promise(resolve => setTimeout(resolve, pollInterval));
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 new Promise(resolve => setTimeout(resolve, pollInterval));
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 new Promise(resolve => setTimeout(resolve, 3000));
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 new Promise(resolve => setTimeout(resolve, 2000));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coffeeinabit",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
4
4
  "description": "CoffeeInABit App",
5
5
  "main": "server.js",
6
6
  "type": "module",
@@ -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(Math.random() * 20000) + 40000
15
+ timeout: Math.floor(randomBetween(40000, 60000))
14
16
  });
15
17
 
16
18
  await page.waitForSelector('main', { timeout: 10000 });
17
- await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 10000) + 500));
19
+ await waitRandom(1200, 3200, page);
18
20
  await page.evaluate(() => { window.scrollTo(0, document.body.scrollHeight / 2); });
19
- await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 4000) + 500));
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.click();
35
- await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 1000) + 500));
36
- await altEditor.fill(commentText);
37
- await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 1000) + 500));
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.click();
43
- await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 2000) + 500));
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.click();
49
- await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 2000) + 500));
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.click();
60
- await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 1000) + 500));
61
- await commentEditor.fill(commentText);
62
- await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 1000) + 500));
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.click();
68
- await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 2000) + 500));
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.click();
74
- await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 2000) + 500));
75
+ await humanLikeClick(page, altSubmitButton, { timeout: 5000 });
76
+ await waitRandom(900, 1600, page);
75
77
  } else {
76
78
  return {
77
79
  action: 'comment_post',
@@ -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(Math.random() * 20000) + 40000
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 new Promise(resolve => setTimeout(resolve, 1000));
24
- await new Promise(resolve => setTimeout(resolve, 2000));
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 new Promise(resolve => setTimeout(resolve, 500));
28
+ await waitRandom(400, 650, page);
28
29
  await page.evaluate(() => { window.scrollTo(0, 300); });
29
- await new Promise(resolve => setTimeout(resolve, 1000));
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 new Promise(resolve => setTimeout(resolve, 500));
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: 'not_connected', message: `Could not find Message button on ${username}'s profile. This usually means you are not connected with this user on LinkedIn. You can only retrieve messages from users who are in your connections.` },
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
+
@@ -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
- await page.waitForSelector('main', { timeout: Math.floor(Math.random() * 5000) + 5000 });
16
- await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 3000) + 500));
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.evaluate((button) => {
35
- if (button) button.click();
36
- }, await likeButton.elementHandle());
36
+ await humanLikeClick(page, likeButton);
37
37
 
38
- await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 2000) + 500));
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 new Promise(resolve => setTimeout(resolve, 2000));
18
+ await waitRandom(1700, 2600, page);
18
19
 
19
20
  await page.evaluate(() => { window.scrollTo(0, 300); });
20
- await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 2000) + 500));
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 new Promise(resolve => setTimeout(resolve, 2000));
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 new Promise(resolve => setTimeout(resolve, 2000));
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 new Promise(resolve => setTimeout(resolve, 5000));
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 locator.click({ trial: false, timeout: 15000 });
173
+ await humanLikeClick(page, locator);
173
174
  return true;
174
175
  } catch (_) {
175
- await new Promise(r => setTimeout(r, 1000 * (attempt + 1)));
176
+ await waitRandom(700 * (attempt + 1), 1200 * (attempt + 1), page);
176
177
  }
177
178
  }
178
- await locator.click();
179
+ await humanLikeClick(page, locator);
179
180
  return true;
180
181
  }
@@ -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(Math.random() * 20000) + 40000
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 new Promise(resolve => setTimeout(resolve, 1000));
30
+ await waitRandom(800, 1400, page);
30
31
  await page.evaluate(() => { window.scrollTo(0, 300); });
31
- await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 2000) + 500));
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 new Promise(r => setTimeout(r, 500));
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 new Promise(r => setTimeout(r, 300));
72
- await btn.click({ timeout: 5000 });
73
- await new Promise(r => setTimeout(r, 800));
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 new Promise(r => setTimeout(r, 300));
91
- await btn.click({ timeout: 5000 });
92
- await new Promise(r => setTimeout(r, 800));
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 new Promise(r => setTimeout(r, 200));
114
- await btn.click({ timeout: 5000 });
115
- await new Promise(r => setTimeout(r, 800));
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 new Promise(r => setTimeout(r, 300));
134
- await btn.click({ timeout: 5000 });
135
- await new Promise(r => setTimeout(r, 800));
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: 'not_connected', message: `Could not find Message button on ${username}'s profile. This usually means you are not connected with this user on LinkedIn.` },
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 new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 100) + 500));
173
- await new Promise(resolve => setTimeout(resolve, 200));
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.click({ timeout: 3000, force: true });
187
- await new Promise(r => setTimeout(r, 200));
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 new Promise(r => setTimeout(r, 150));
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 new Promise(r => setTimeout(r, 300));
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 new Promise(r => setTimeout(r, 500));
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 new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 300) + 300));
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 new Promise(resolve => setTimeout(resolve, 500));
237
- await editor.click();
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 new Promise(resolve => setTimeout(resolve, 200));
243
+ await waitRandom(180, 260, page);
243
244
  console.log('[send_messages] Retrying to type message');
244
245
  await humanLikeType(editor, message);
245
- await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 300) + 300));
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 new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 200) + 200));
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 new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 1000) + 500));
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 new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 100) + 50));
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 new Promise(resolve => setTimeout(resolve, 1000));
292
-
292
+ await waitRandom(900, 1200, page);
293
+
293
294
  console.log('[send_messages] findAndClickMessageInput: Looking for editor by aria-label');
294
- const editorClicked = await page.evaluate(() => {
295
- const editor = document.querySelector('[aria-label="Write a message…"]');
296
- if (editor) {
297
- console.log('[send_messages] Found editor, focusing and clicking');
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 placeholderClicked = await page.evaluate(() => {
315
- const placeholder = document.querySelector('.msg-form__placeholder');
316
- if (placeholder) {
317
- console.log('[send_messages] Found placeholder, clicking');
318
- placeholder.click();
319
- const editor = document.querySelector('[aria-label="Write a message…"]');
320
- if (editor) {
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
- console.log('[send_messages] Placeholder not found');
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
- await page.evaluate(() => {
341
- const allButtons = document.querySelectorAll('button');
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
- await new Promise(resolve => setTimeout(resolve, 500));
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 new Promise(resolve => setTimeout(resolve, 500));
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 clicked = await page.evaluate(() => {
365
- const sendButton = document.querySelector('.msg-form__send-button:not([disabled])');
366
- if (sendButton && !sendButton.disabled) {
367
- console.log('[send_messages] Found enabled send button, clicking');
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 new Promise(resolve => setTimeout(resolve, 300));
347
+ await waitRandom(260, 420, page);
381
348
  }
382
349
 
383
350
  console.error('[send_messages] Failed to click send button after 10 attempts');