coffeeinabit 0.0.20 → 0.0.22
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 +111 -14
- package/package.json +1 -1
- package/public/dashboard.html +5 -7
- package/tools/comment_post.js +71 -8
- package/tools/get_messages.js +21 -5
- package/tools/get_profile.js +72 -13
- package/tools/like_post.js +46 -6
- package/tools/send_connection_request.js +65 -7
- package/tools/send_messages.js +66 -26
package/linkedin_automation.js
CHANGED
|
@@ -51,6 +51,31 @@ export class LinkedInAutomation {
|
|
|
51
51
|
return delay;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
hasActiveBrowser() {
|
|
55
|
+
const contextOpen = this.context && typeof this.context.isClosed === 'function' ? !this.context.isClosed() : Boolean(this.context);
|
|
56
|
+
const pageOpen = this.page && typeof this.page.isClosed === 'function' ? !this.page.isClosed() : Boolean(this.page);
|
|
57
|
+
return Boolean(this.browser && contextOpen && pageOpen);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
ensureBrowserAvailable(context) {
|
|
61
|
+
if (this.hasActiveBrowser()) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
this.handleBrowserUnavailable(context);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
handleBrowserUnavailable(context) {
|
|
69
|
+
console.warn(`[LinkedInAutomation] Browser unavailable during ${context}. Stopping background activities...`);
|
|
70
|
+
this.stopActionPolling();
|
|
71
|
+
this.stopScreenshotCapture();
|
|
72
|
+
this.stopAmbientMouseMovements();
|
|
73
|
+
if (this.status !== 'browser_unavailable') {
|
|
74
|
+
this.status = 'browser_unavailable';
|
|
75
|
+
this.emitStatus();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
54
79
|
getLinkedInSessionPath() {
|
|
55
80
|
const sanitizedEmail = this.userEmail.replace(/[^a-zA-Z0-9]/g, '_');
|
|
56
81
|
return path.join(this.linkedInSessionDir, `linkedin_auth_${sanitizedEmail}.json`);
|
|
@@ -183,7 +208,7 @@ export class LinkedInAutomation {
|
|
|
183
208
|
}
|
|
184
209
|
}
|
|
185
210
|
|
|
186
|
-
async startAutomation(headless =
|
|
211
|
+
async startAutomation(headless = true) {
|
|
187
212
|
try {
|
|
188
213
|
console.log('[LinkedInAutomation] Starting automation...');
|
|
189
214
|
this.headless = headless;
|
|
@@ -461,6 +486,9 @@ export class LinkedInAutomation {
|
|
|
461
486
|
}
|
|
462
487
|
|
|
463
488
|
startScreenshotCapture() {
|
|
489
|
+
if (!this.ensureBrowserAvailable('startScreenshotCapture')) {
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
464
492
|
console.log('[LinkedInAutomation] Starting screenshot capture...');
|
|
465
493
|
|
|
466
494
|
this.screenshotInterval = setInterval(async () => {
|
|
@@ -477,7 +505,9 @@ export class LinkedInAutomation {
|
|
|
477
505
|
}
|
|
478
506
|
|
|
479
507
|
async captureScreenshot() {
|
|
480
|
-
if (!this.
|
|
508
|
+
if (!this.ensureBrowserAvailable('captureScreenshot')) {
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
481
511
|
|
|
482
512
|
try {
|
|
483
513
|
const screenshot = await this.page.screenshot({
|
|
@@ -498,6 +528,9 @@ export class LinkedInAutomation {
|
|
|
498
528
|
|
|
499
529
|
} catch (error) {
|
|
500
530
|
console.error('[LinkedInAutomation] Failed to capture screenshot:', error.message);
|
|
531
|
+
if (typeof error.message === 'string' && error.message.includes('Target page, context or browser has been closed')) {
|
|
532
|
+
this.handleBrowserUnavailable('captureScreenshot_error');
|
|
533
|
+
}
|
|
501
534
|
}
|
|
502
535
|
}
|
|
503
536
|
|
|
@@ -506,6 +539,9 @@ export class LinkedInAutomation {
|
|
|
506
539
|
console.log('[LinkedInAutomation] Action polling is already active, skipping...');
|
|
507
540
|
return;
|
|
508
541
|
}
|
|
542
|
+
if (!this.ensureBrowserAvailable('startActionPolling')) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
509
545
|
|
|
510
546
|
console.log('[LinkedInAutomation] Starting action polling with random intervals (20-40 seconds)...');
|
|
511
547
|
this.isActionPollingActive = true;
|
|
@@ -540,6 +576,9 @@ export class LinkedInAutomation {
|
|
|
540
576
|
}
|
|
541
577
|
|
|
542
578
|
async pollForActions() {
|
|
579
|
+
if (!this.ensureBrowserAvailable('pollForActions')) {
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
543
582
|
try {
|
|
544
583
|
console.log('[LinkedInAutomation] Polling for actions from backend...');
|
|
545
584
|
|
|
@@ -587,14 +626,48 @@ export class LinkedInAutomation {
|
|
|
587
626
|
}
|
|
588
627
|
|
|
589
628
|
async executeAction(action) {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
629
|
+
const actionLogs = [];
|
|
630
|
+
const originalConsoleLog = console.log;
|
|
631
|
+
const originalConsoleError = console.error;
|
|
632
|
+
const originalConsoleWarn = console.warn;
|
|
633
|
+
|
|
634
|
+
const captureLog = (level, args) => {
|
|
635
|
+
const timestamp = new Date().toISOString();
|
|
636
|
+
const message = args.map(arg => {
|
|
637
|
+
if (typeof arg === 'string') return arg;
|
|
638
|
+
if (arg instanceof Error) return `${arg.message}\n${arg.stack}`;
|
|
639
|
+
try {
|
|
640
|
+
return JSON.stringify(arg);
|
|
641
|
+
} catch {
|
|
642
|
+
return String(arg);
|
|
643
|
+
}
|
|
644
|
+
}).join(' ');
|
|
645
|
+
actionLogs.push({ timestamp, level, message });
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
console.log = (...args) => {
|
|
649
|
+
captureLog('log', args);
|
|
650
|
+
originalConsoleLog.apply(console, args);
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
console.error = (...args) => {
|
|
654
|
+
captureLog('error', args);
|
|
655
|
+
originalConsoleError.apply(console, args);
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
console.warn = (...args) => {
|
|
659
|
+
captureLog('warn', args);
|
|
660
|
+
originalConsoleWarn.apply(console, args);
|
|
661
|
+
};
|
|
596
662
|
|
|
597
663
|
try {
|
|
664
|
+
console.log('[LinkedInAutomation] Starting execution of action:', action.action, 'ID:', action.action_id);
|
|
665
|
+
console.log('[LinkedInAutomation] Action parameters:', JSON.stringify(action.parameters, null, 2));
|
|
666
|
+
this.currentAction = action;
|
|
667
|
+
this.status = 'executing_action';
|
|
668
|
+
this.emitActionUpdate();
|
|
669
|
+
this.emitStatus();
|
|
670
|
+
|
|
598
671
|
let result = null;
|
|
599
672
|
|
|
600
673
|
switch (action.action) {
|
|
@@ -654,16 +727,34 @@ export class LinkedInAutomation {
|
|
|
654
727
|
}
|
|
655
728
|
|
|
656
729
|
console.log('[LinkedInAutomation] Reporting action result for ID:', action.action_id);
|
|
730
|
+
|
|
731
|
+
if (result && typeof result === 'object') {
|
|
732
|
+
result.action_logs = actionLogs;
|
|
733
|
+
} else {
|
|
734
|
+
result = { ...result, action_logs: actionLogs };
|
|
735
|
+
}
|
|
736
|
+
|
|
657
737
|
await this.reportActionResult(action.action_id, result, action);
|
|
658
738
|
console.log('[LinkedInAutomation] Action completed successfully:', action.action);
|
|
659
739
|
|
|
660
740
|
} catch (error) {
|
|
661
741
|
console.error('[LinkedInAutomation] Error executing action:', action.action, 'Error:', error.message);
|
|
662
|
-
|
|
742
|
+
console.error('[LinkedInAutomation] Error stack:', error.stack);
|
|
743
|
+
|
|
744
|
+
const errorResult = {
|
|
745
|
+
error: error.message,
|
|
746
|
+
error_stack: error.stack,
|
|
747
|
+
action_logs: actionLogs
|
|
748
|
+
};
|
|
749
|
+
await this.reportActionResult(action.action_id, errorResult, action);
|
|
750
|
+
} finally {
|
|
751
|
+
console.log = originalConsoleLog;
|
|
752
|
+
console.error = originalConsoleError;
|
|
753
|
+
console.warn = originalConsoleWarn;
|
|
754
|
+
|
|
755
|
+
this.currentAction = null;
|
|
756
|
+
this.emitActionUpdate();
|
|
663
757
|
}
|
|
664
|
-
|
|
665
|
-
this.currentAction = null;
|
|
666
|
-
this.emitActionUpdate();
|
|
667
758
|
}
|
|
668
759
|
|
|
669
760
|
|
|
@@ -999,12 +1090,15 @@ export class LinkedInAutomation {
|
|
|
999
1090
|
|
|
1000
1091
|
|
|
1001
1092
|
startAmbientMouseMovements() {
|
|
1002
|
-
if (this.ambientMouseTimeout
|
|
1093
|
+
if (this.ambientMouseTimeout) {
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
if (!this.ensureBrowserAvailable('startAmbientMouseMovements')) {
|
|
1003
1097
|
return;
|
|
1004
1098
|
}
|
|
1005
1099
|
|
|
1006
1100
|
const scheduleMove = async () => {
|
|
1007
|
-
if (!this.
|
|
1101
|
+
if (!this.ensureBrowserAvailable('ambientMouseMovement') || !this.isRunning) {
|
|
1008
1102
|
this.ambientMouseTimeout = null;
|
|
1009
1103
|
return;
|
|
1010
1104
|
}
|
|
@@ -1013,6 +1107,9 @@ export class LinkedInAutomation {
|
|
|
1013
1107
|
await humanLikeAmbientMove(this.page);
|
|
1014
1108
|
} catch (error) {
|
|
1015
1109
|
console.warn('[LinkedInAutomation] Ambient mouse move failed:', error.message);
|
|
1110
|
+
if (typeof error.message === 'string' && error.message.includes('Target page, context or browser has been closed')) {
|
|
1111
|
+
this.handleBrowserUnavailable('ambientMouseMovement_error');
|
|
1112
|
+
}
|
|
1016
1113
|
}
|
|
1017
1114
|
|
|
1018
1115
|
const nextDelay = Math.floor(Math.random() * 7000) + 6000;
|
package/package.json
CHANGED
package/public/dashboard.html
CHANGED
|
@@ -168,14 +168,12 @@
|
|
|
168
168
|
const keepBrowserToggle = document.getElementById('keepBrowserToggle');
|
|
169
169
|
|
|
170
170
|
if (keepBrowserEnabled === null) {
|
|
171
|
-
localStorage.setItem('keepBrowser', '
|
|
171
|
+
localStorage.setItem('keepBrowser', 'false');
|
|
172
172
|
if (keepBrowserToggle) {
|
|
173
|
-
keepBrowserToggle.checked =
|
|
174
|
-
}
|
|
175
|
-
} else if (keepBrowserEnabled === 'true') {
|
|
176
|
-
if (keepBrowserToggle) {
|
|
177
|
-
keepBrowserToggle.checked = true;
|
|
173
|
+
keepBrowserToggle.checked = false;
|
|
178
174
|
}
|
|
175
|
+
} else if (keepBrowserToggle) {
|
|
176
|
+
keepBrowserToggle.checked = keepBrowserEnabled === 'true';
|
|
179
177
|
}
|
|
180
178
|
}
|
|
181
179
|
|
|
@@ -334,7 +332,7 @@
|
|
|
334
332
|
startAutomationBtn.disabled = true;
|
|
335
333
|
startAutomationBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Starting...';
|
|
336
334
|
|
|
337
|
-
const keepBrowser = localStorage.getItem('keepBrowser')
|
|
335
|
+
const keepBrowser = localStorage.getItem('keepBrowser') === 'true';
|
|
338
336
|
|
|
339
337
|
const response = await fetch('/api/automation/start', {
|
|
340
338
|
method: 'POST',
|
package/tools/comment_post.js
CHANGED
|
@@ -6,84 +6,147 @@ export async function executeCommentPost(page, action) {
|
|
|
6
6
|
const postUrl = action.parameters?.url || action.url;
|
|
7
7
|
const commentText = action.parameters?.comment_text || action.comment_text;
|
|
8
8
|
|
|
9
|
+
console.log('[CommentPost] Starting comment post action');
|
|
10
|
+
console.log('[CommentPost] Post URL:', postUrl);
|
|
11
|
+
console.log('[CommentPost] Comment text length:', commentText?.length || 0);
|
|
12
|
+
|
|
9
13
|
if (!postUrl || !commentText) {
|
|
14
|
+
console.error('[CommentPost] Missing required parameters');
|
|
10
15
|
throw new Error('Missing url or comment_text for comment_post');
|
|
11
16
|
}
|
|
12
17
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
console.log('[CommentPost] Navigating to post URL');
|
|
19
|
+
console.log('[CommentPost] Will wait up to 60 seconds for page load');
|
|
20
|
+
try {
|
|
21
|
+
await safeGoto(page, postUrl, {
|
|
22
|
+
waitUntil: 'domcontentloaded',
|
|
23
|
+
timeout: Math.floor(randomBetween(40000, 60000))
|
|
24
|
+
});
|
|
25
|
+
console.log('[CommentPost] Post page loaded successfully');
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('[CommentPost] Navigation error:', error.message);
|
|
28
|
+
if (error.message.includes('timeout') || error.message.includes('Timeout')) {
|
|
29
|
+
console.error('[CommentPost] Network timeout during post page navigation');
|
|
30
|
+
throw new Error(`Network timeout while navigating to post: ${error.message}`);
|
|
31
|
+
}
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
17
34
|
|
|
18
|
-
|
|
35
|
+
console.log('[CommentPost] Waiting for main content to load');
|
|
36
|
+
try {
|
|
37
|
+
await page.waitForSelector('main', { timeout: 10000 });
|
|
38
|
+
console.log('[CommentPost] Main content loaded');
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('[CommentPost] Main content not found:', error.message);
|
|
41
|
+
if (error.message.includes('timeout')) {
|
|
42
|
+
console.error('[CommentPost] Timeout waiting for main content');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
19
45
|
await waitRandom(1200, 3200, page);
|
|
46
|
+
console.log('[CommentPost] Scrolling to middle of page');
|
|
20
47
|
await page.evaluate(() => { window.scrollTo(0, document.body.scrollHeight / 2); });
|
|
21
48
|
await waitRandom(900, 2100, page);
|
|
22
49
|
|
|
50
|
+
console.log('[CommentPost] Searching for comment editor');
|
|
23
51
|
let commentEditor = await page.locator('.comments-comment-box-comment__text-editor .ql-editor[contenteditable="true"]').first();
|
|
24
52
|
|
|
25
53
|
if (await commentEditor.count() === 0) {
|
|
54
|
+
console.log('[CommentPost] Primary editor not found, trying alternative selector');
|
|
26
55
|
const altEditor = await page.locator('[data-test-ql-editor-contenteditable="true"]').first();
|
|
27
56
|
|
|
28
57
|
if (await altEditor.count() === 0) {
|
|
58
|
+
console.error('[CommentPost] Comment editor not found with any selector');
|
|
29
59
|
return {
|
|
30
60
|
action: 'comment_post',
|
|
31
|
-
result: {
|
|
61
|
+
result: {
|
|
62
|
+
status: 'not_found',
|
|
63
|
+
message: `Could not find comment editor for post: ${postUrl}`,
|
|
64
|
+
details: 'Comment editor element not found, page may not have loaded or UI changed'
|
|
65
|
+
},
|
|
32
66
|
status: 'failed'
|
|
33
67
|
};
|
|
34
68
|
}
|
|
35
69
|
|
|
70
|
+
console.log('[CommentPost] Alternative editor found, clicking and typing comment');
|
|
36
71
|
await humanLikeClick(page, altEditor, { timeout: 5000 });
|
|
37
72
|
await waitRandom(600, 900, page);
|
|
73
|
+
console.log('[CommentPost] Typing comment text');
|
|
38
74
|
await humanLikeType(altEditor, commentText);
|
|
39
75
|
await waitRandom(600, 900, page);
|
|
76
|
+
console.log('[CommentPost] Comment typed successfully, searching for submit button');
|
|
40
77
|
|
|
41
78
|
let submitButton = await page.locator('.comments-comment-box__submit-button--cr').first();
|
|
42
79
|
|
|
43
80
|
if (await submitButton.count() > 0) {
|
|
81
|
+
console.log('[CommentPost] Submit button found, clicking');
|
|
44
82
|
await humanLikeClick(page, submitButton, { timeout: 5000 });
|
|
45
83
|
await waitRandom(900, 1600, page);
|
|
84
|
+
console.log('[CommentPost] Submit button clicked, comment should be posted');
|
|
46
85
|
} else {
|
|
86
|
+
console.log('[CommentPost] Primary submit button not found, trying alternative');
|
|
47
87
|
const altSubmitButton = await page.locator('button:has-text("Comment")').first();
|
|
48
88
|
|
|
49
89
|
if (await altSubmitButton.count() > 0) {
|
|
90
|
+
console.log('[CommentPost] Alternative submit button found, clicking');
|
|
50
91
|
await humanLikeClick(page, altSubmitButton, { timeout: 5000 });
|
|
51
92
|
await waitRandom(900, 1600, page);
|
|
93
|
+
console.log('[CommentPost] Comment submit button clicked');
|
|
52
94
|
} else {
|
|
95
|
+
console.error('[CommentPost] Submit button not found after typing comment');
|
|
96
|
+
console.error('[CommentPost] Comment may have been typed but not submitted');
|
|
53
97
|
return {
|
|
54
98
|
action: 'comment_post',
|
|
55
|
-
result: {
|
|
99
|
+
result: {
|
|
100
|
+
status: 'submit_not_found',
|
|
101
|
+
message: `Could not find Comment submit button for post: ${postUrl}`,
|
|
102
|
+
details: 'Comment was typed but submit button not found. Comment may not have been posted.'
|
|
103
|
+
},
|
|
56
104
|
status: 'failed'
|
|
57
105
|
};
|
|
58
106
|
}
|
|
59
107
|
}
|
|
60
108
|
} else {
|
|
109
|
+
console.log('[CommentPost] Primary editor found, clicking and typing comment');
|
|
61
110
|
await humanLikeClick(page, commentEditor, { timeout: 5000 });
|
|
62
111
|
await waitRandom(600, 900, page);
|
|
112
|
+
console.log('[CommentPost] Typing comment text');
|
|
63
113
|
await humanLikeType(commentEditor, commentText);
|
|
64
114
|
await waitRandom(600, 900, page);
|
|
115
|
+
console.log('[CommentPost] Comment typed successfully, searching for submit button');
|
|
65
116
|
|
|
66
117
|
let submitButton = await page.locator('.comments-comment-box__submit-button--cr').first();
|
|
67
118
|
|
|
68
119
|
if (await submitButton.count() > 0) {
|
|
120
|
+
console.log('[CommentPost] Submit button found, clicking');
|
|
69
121
|
await humanLikeClick(page, submitButton, { timeout: 5000 });
|
|
70
122
|
await waitRandom(900, 1600, page);
|
|
123
|
+
console.log('[CommentPost] Submit button clicked, comment should be posted');
|
|
71
124
|
} else {
|
|
125
|
+
console.log('[CommentPost] Primary submit button not found, trying alternative');
|
|
72
126
|
const altSubmitButton = await page.locator('button:has-text("Comment")').first();
|
|
73
127
|
|
|
74
128
|
if (await altSubmitButton.count() > 0) {
|
|
129
|
+
console.log('[CommentPost] Alternative submit button found, clicking');
|
|
75
130
|
await humanLikeClick(page, altSubmitButton, { timeout: 5000 });
|
|
76
131
|
await waitRandom(900, 1600, page);
|
|
132
|
+
console.log('[CommentPost] Comment submit button clicked');
|
|
77
133
|
} else {
|
|
134
|
+
console.error('[CommentPost] Submit button not found after typing comment');
|
|
135
|
+
console.error('[CommentPost] Comment may have been typed but not submitted');
|
|
78
136
|
return {
|
|
79
137
|
action: 'comment_post',
|
|
80
|
-
result: {
|
|
138
|
+
result: {
|
|
139
|
+
status: 'submit_not_found',
|
|
140
|
+
message: `Could not find Comment submit button for post: ${postUrl}`,
|
|
141
|
+
details: 'Comment was typed but submit button not found. Comment may not have been posted.'
|
|
142
|
+
},
|
|
81
143
|
status: 'failed'
|
|
82
144
|
};
|
|
83
145
|
}
|
|
84
146
|
}
|
|
85
147
|
}
|
|
86
148
|
|
|
149
|
+
console.log('[CommentPost] Comment post action completed successfully');
|
|
87
150
|
return {
|
|
88
151
|
action: 'comment_post',
|
|
89
152
|
result: { status: 'success', message: `Comment successfully posted on post: ${postUrl}` },
|
package/tools/get_messages.js
CHANGED
|
@@ -14,11 +14,21 @@ export async function executeGetMessages(page, action) {
|
|
|
14
14
|
|
|
15
15
|
const profileUrl = `https://www.linkedin.com/in/${username}`;
|
|
16
16
|
console.log('[get_messages] Navigating to profile:', profileUrl);
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
console.log('[get_messages] Will wait up to 60 seconds for page load');
|
|
18
|
+
try {
|
|
19
|
+
await safeGoto(page, profileUrl, {
|
|
20
|
+
waitUntil: 'load',
|
|
21
|
+
timeout: Math.floor(randomBetween(40000, 60000))
|
|
22
|
+
});
|
|
23
|
+
console.log('[get_messages] Page loaded successfully, URL:', page.url());
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error('[get_messages] Navigation error:', error.message);
|
|
26
|
+
if (error.message.includes('timeout') || error.message.includes('Timeout')) {
|
|
27
|
+
console.error('[get_messages] Network timeout during profile navigation');
|
|
28
|
+
throw new Error(`Network timeout while navigating to profile: ${error.message}`);
|
|
29
|
+
}
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
22
32
|
|
|
23
33
|
await page.waitForLoadState('domcontentloaded');
|
|
24
34
|
await waitRandom(900, 1400, page);
|
|
@@ -32,12 +42,18 @@ export async function executeGetMessages(page, action) {
|
|
|
32
42
|
console.log('[get_messages] Closing all conversation bubbles');
|
|
33
43
|
await closeAllConversationBubbles(page);
|
|
34
44
|
|
|
45
|
+
console.log('[get_messages] Waiting for message button to appear');
|
|
35
46
|
try {
|
|
36
47
|
await page.waitForSelector('button[aria-label^="Message"], main button.artdeco-button--primary', {
|
|
37
48
|
timeout: 5000,
|
|
38
49
|
state: 'attached'
|
|
39
50
|
});
|
|
51
|
+
console.log('[get_messages] Message button selector found');
|
|
40
52
|
} catch (error) {
|
|
53
|
+
console.warn('[get_messages] Message button selector not found immediately, will continue searching');
|
|
54
|
+
if (error.message.includes('timeout')) {
|
|
55
|
+
console.warn('[get_messages] Timeout waiting for message button selector');
|
|
56
|
+
}
|
|
41
57
|
}
|
|
42
58
|
|
|
43
59
|
await waitRandom(400, 650, page);
|
package/tools/get_profile.js
CHANGED
|
@@ -14,49 +14,85 @@ export async function executeGetProfile(page, action) {
|
|
|
14
14
|
|
|
15
15
|
try{
|
|
16
16
|
if (username === 'self') {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
console.log('[GetProfile] Fetching self profile, navigating to feed page');
|
|
18
|
+
try {
|
|
19
|
+
await safeGoto(page, 'https://www.linkedin.com/feed/', {
|
|
20
|
+
waitUntil: 'domcontentloaded',
|
|
21
|
+
timeout: 60000
|
|
22
|
+
});
|
|
23
|
+
console.log('[GetProfile] Feed page loaded successfully');
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error('[GetProfile] Navigation error to feed page:', error.message);
|
|
26
|
+
if (error.message.includes('timeout') || error.message.includes('Timeout')) {
|
|
27
|
+
console.error('[GetProfile] Network timeout during feed page navigation');
|
|
28
|
+
throw new Error(`Network timeout while navigating to feed: ${error.message}`);
|
|
29
|
+
}
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
21
32
|
|
|
22
33
|
await page.waitForLoadState('domcontentloaded');
|
|
23
34
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
24
35
|
|
|
36
|
+
console.log('[GetProfile] Searching for profile card on feed page');
|
|
25
37
|
const profileCard = await page.locator('.profile-card-member-details').first();
|
|
26
38
|
if (await profileCard.count() === 0) {
|
|
39
|
+
console.error('[GetProfile] Profile card not found on feed page');
|
|
27
40
|
throw new Error('Could not find profile card on /feed');
|
|
28
41
|
}
|
|
42
|
+
console.log('[GetProfile] Profile card found');
|
|
29
43
|
|
|
44
|
+
console.log('[GetProfile] Searching for profile name link');
|
|
30
45
|
const profileNameLink = await profileCard.locator('.profile-card-name').first();
|
|
31
46
|
if (await profileNameLink.count() === 0) {
|
|
47
|
+
console.error('[GetProfile] Profile name link not found');
|
|
32
48
|
throw new Error('Could not find profile name link on /feed');
|
|
33
49
|
}
|
|
50
|
+
console.log('[GetProfile] Profile name link found, clicking to navigate to profile');
|
|
34
51
|
|
|
35
52
|
await profileNameLink.click();
|
|
36
53
|
await page.waitForLoadState('domcontentloaded');
|
|
37
54
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
38
55
|
|
|
39
56
|
const currentUrl = page.url();
|
|
57
|
+
console.log('[GetProfile] Extracted profile URL:', currentUrl);
|
|
40
58
|
const match = currentUrl.match(/\/in\/([^/]+)/);
|
|
41
59
|
if (!match) {
|
|
60
|
+
console.error('[GetProfile] Could not extract username from URL:', currentUrl);
|
|
42
61
|
throw new Error('Could not extract username from profile URL');
|
|
43
62
|
}
|
|
44
63
|
username = match[1];
|
|
64
|
+
console.log('[GetProfile] Extracted username:', username);
|
|
45
65
|
}
|
|
46
66
|
|
|
47
67
|
const profileUrl = `https://www.linkedin.com/in/${username}`;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
68
|
+
console.log('[GetProfile] Navigating to profile URL:', profileUrl);
|
|
69
|
+
console.log('[GetProfile] Will wait up to 60 seconds for page load');
|
|
70
|
+
try {
|
|
71
|
+
await safeGoto(page, profileUrl, {
|
|
72
|
+
waitUntil: 'domcontentloaded',
|
|
73
|
+
timeout: 60000
|
|
74
|
+
});
|
|
75
|
+
console.log('[GetProfile] Profile page loaded successfully');
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error('[GetProfile] Navigation error to profile:', error.message);
|
|
78
|
+
if (error.message.includes('timeout') || error.message.includes('Timeout')) {
|
|
79
|
+
console.error('[GetProfile] Network timeout during profile page navigation');
|
|
80
|
+
throw new Error(`Network timeout while navigating to profile: ${error.message}`);
|
|
81
|
+
}
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
52
84
|
|
|
53
85
|
await page.waitForLoadState('domcontentloaded');
|
|
54
86
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
87
|
+
console.log('[GetProfile] Page fully loaded, starting to scroll and extract content');
|
|
55
88
|
|
|
89
|
+
console.log('[GetProfile] Scrolling down to load profile content');
|
|
56
90
|
await gentleScroll(page, 'down');
|
|
57
91
|
await waitRandom(260, 480, page);
|
|
92
|
+
console.log('[GetProfile] Scrolling up');
|
|
58
93
|
await gentleScroll(page, 'up');
|
|
59
94
|
await waitRandom(240, 420, page);
|
|
95
|
+
console.log('[GetProfile] Extracting profile text content');
|
|
60
96
|
|
|
61
97
|
const profileText = await page.evaluate(() => {
|
|
62
98
|
const mainContent = document.querySelector('main');
|
|
@@ -75,21 +111,35 @@ export async function executeGetProfile(page, action) {
|
|
|
75
111
|
return getAllText(mainContent);
|
|
76
112
|
});
|
|
77
113
|
|
|
114
|
+
console.log('[GetProfile] Getting dropdown options and connection status');
|
|
78
115
|
const { moreButtonOptions, isFirstDegreeConnection } = await getDropdownOptions(page);
|
|
116
|
+
console.log('[GetProfile] Dropdown options retrieved, isFirstDegreeConnection:', isFirstDegreeConnection);
|
|
79
117
|
|
|
80
118
|
const recentActivityUrl = `https://www.linkedin.com/in/${username}/recent-activity/all/`;
|
|
119
|
+
console.log('[GetProfile] Navigating to recent activity page:', recentActivityUrl);
|
|
81
120
|
let recentPosts = {};
|
|
82
121
|
|
|
83
122
|
try {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
123
|
+
try {
|
|
124
|
+
await safeGoto(page, recentActivityUrl, {
|
|
125
|
+
waitUntil: 'domcontentloaded',
|
|
126
|
+
timeout: 60000
|
|
127
|
+
});
|
|
128
|
+
console.log('[GetProfile] Recent activity page loaded successfully');
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error('[GetProfile] Navigation error to recent activity:', error.message);
|
|
131
|
+
if (error.message.includes('timeout') || error.message.includes('Timeout')) {
|
|
132
|
+
console.error('[GetProfile] Network timeout during recent activity page navigation');
|
|
133
|
+
}
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
88
136
|
|
|
89
137
|
await page.waitForLoadState('domcontentloaded');
|
|
90
138
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
91
139
|
|
|
140
|
+
console.log('[GetProfile] Scrolling recent activity page to load posts');
|
|
92
141
|
await gentleScroll(page, 'down', { stepRange: [140, 260], pauseRange: [80, 160], maxIterations: 40 });
|
|
142
|
+
console.log('[GetProfile] Extracting recent posts');
|
|
93
143
|
|
|
94
144
|
recentPosts = await page.evaluate(() => {
|
|
95
145
|
const mainContent = document.querySelector('main');
|
|
@@ -149,8 +199,13 @@ export async function executeGetProfile(page, action) {
|
|
|
149
199
|
});
|
|
150
200
|
|
|
151
201
|
} catch (error) {
|
|
202
|
+
console.warn('[GetProfile] Error fetching recent activity:', error.message);
|
|
203
|
+
if (error.message.includes('timeout') || error.message.includes('Timeout')) {
|
|
204
|
+
console.warn('[GetProfile] Network timeout while fetching recent activity, continuing with profile data only');
|
|
205
|
+
}
|
|
152
206
|
}
|
|
153
207
|
|
|
208
|
+
console.log('[GetProfile] Profile data extraction completed successfully');
|
|
154
209
|
return {
|
|
155
210
|
profileText,
|
|
156
211
|
recentPosts,
|
|
@@ -159,7 +214,11 @@ export async function executeGetProfile(page, action) {
|
|
|
159
214
|
};
|
|
160
215
|
|
|
161
216
|
} catch (error) {
|
|
162
|
-
console.error('[GetProfile]
|
|
217
|
+
console.error('[GetProfile] Fatal error during profile retrieval:', error.message);
|
|
218
|
+
console.error('[GetProfile] Error stack:', error.stack);
|
|
219
|
+
if (error.message.includes('timeout') || error.message.includes('Timeout')) {
|
|
220
|
+
console.error('[GetProfile] Network timeout detected - profile page may not have loaded completely');
|
|
221
|
+
}
|
|
163
222
|
throw error;
|
|
164
223
|
}
|
|
165
224
|
}
|
package/tools/like_post.js
CHANGED
|
@@ -4,38 +4,71 @@ import { humanLikeClick, waitRandom, randomBetween } from './human_mouse.js';
|
|
|
4
4
|
export async function executeLikePost(page, action) {
|
|
5
5
|
const postUrl = action.parameters?.url || action.url;
|
|
6
6
|
|
|
7
|
+
console.log('[LikePost] Starting like post action');
|
|
8
|
+
console.log('[LikePost] Post URL:', postUrl);
|
|
9
|
+
|
|
7
10
|
if (!postUrl) {
|
|
11
|
+
console.error('[LikePost] Missing required parameter: url');
|
|
8
12
|
throw new Error('Missing url for like_post');
|
|
9
13
|
}
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
console.log('[LikePost] Navigating to post URL');
|
|
16
|
+
console.log('[LikePost] Will wait up to 60 seconds for page load');
|
|
17
|
+
try {
|
|
18
|
+
await safeGoto(page, postUrl, {
|
|
19
|
+
waitUntil: 'domcontentloaded',
|
|
20
|
+
timeout: 60000
|
|
21
|
+
});
|
|
22
|
+
console.log('[LikePost] Post page loaded successfully');
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.error('[LikePost] Navigation error:', error.message);
|
|
25
|
+
if (error.message.includes('timeout') || error.message.includes('Timeout')) {
|
|
26
|
+
console.error('[LikePost] Network timeout during post page navigation');
|
|
27
|
+
throw new Error(`Network timeout while navigating to post: ${error.message}`);
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
15
31
|
|
|
32
|
+
console.log('[LikePost] Waiting for main content to load');
|
|
16
33
|
const mainTimeout = Math.floor(randomBetween(5000, 9000));
|
|
17
|
-
|
|
34
|
+
try {
|
|
35
|
+
await page.waitForSelector('main', { timeout: mainTimeout });
|
|
36
|
+
console.log('[LikePost] Main content loaded');
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.warn('[LikePost] Main content not found within timeout:', error.message);
|
|
39
|
+
if (error.message.includes('timeout')) {
|
|
40
|
+
console.warn('[LikePost] Timeout waiting for main content');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
18
43
|
await waitRandom(600, 3200, page);
|
|
19
44
|
|
|
45
|
+
console.log('[LikePost] Searching for Like button');
|
|
20
46
|
let likeButton = await page.locator('main button[aria-label="React Like"], main button[aria-label="Unreact Like"]').first();
|
|
21
47
|
|
|
22
48
|
if (await likeButton.count() === 0) {
|
|
49
|
+
console.log('[LikePost] Primary Like button not found, trying alternative selector');
|
|
23
50
|
likeButton = await page.locator('main button:has-text("Like"), main button:has-text("Unlike")').first();
|
|
24
51
|
}
|
|
25
52
|
|
|
26
53
|
if (await likeButton.count() > 0) {
|
|
54
|
+
console.log('[LikePost] Like button found, checking current state');
|
|
27
55
|
const ariaLabel = await likeButton.getAttribute('aria-label');
|
|
56
|
+
console.log('[LikePost] Like button aria-label:', ariaLabel);
|
|
28
57
|
|
|
29
58
|
if (ariaLabel === 'Unreact Like') {
|
|
59
|
+
console.log('[LikePost] Post is already liked');
|
|
30
60
|
return {
|
|
31
61
|
action: 'like_post',
|
|
32
62
|
result: { status: 'already_liked', message: `Post is already liked: ${postUrl}` },
|
|
33
63
|
status: 'success'
|
|
34
64
|
};
|
|
35
65
|
} else if (ariaLabel === 'React Like') {
|
|
66
|
+
console.log('[LikePost] Post not liked yet, clicking Like button');
|
|
36
67
|
await humanLikeClick(page, likeButton);
|
|
68
|
+
console.log('[LikePost] Like button clicked successfully');
|
|
37
69
|
|
|
38
70
|
await waitRandom(700, 2400, page);
|
|
71
|
+
console.log('[LikePost] Like action completed');
|
|
39
72
|
|
|
40
73
|
return {
|
|
41
74
|
action: 'like_post',
|
|
@@ -43,6 +76,7 @@ export async function executeLikePost(page, action) {
|
|
|
43
76
|
status: 'success'
|
|
44
77
|
};
|
|
45
78
|
} else {
|
|
79
|
+
console.warn('[LikePost] Could not determine Like button state, aria-label:', ariaLabel);
|
|
46
80
|
return {
|
|
47
81
|
action: 'like_post',
|
|
48
82
|
result: { status: 'unknown', message: `Could not determine Like button state for post: ${postUrl}` },
|
|
@@ -50,9 +84,15 @@ export async function executeLikePost(page, action) {
|
|
|
50
84
|
};
|
|
51
85
|
}
|
|
52
86
|
} else {
|
|
87
|
+
console.error('[LikePost] Like button not found with any selector');
|
|
88
|
+
console.error('[LikePost] Post may not have loaded or UI has changed');
|
|
53
89
|
return {
|
|
54
90
|
action: 'like_post',
|
|
55
|
-
result: {
|
|
91
|
+
result: {
|
|
92
|
+
status: 'not_found',
|
|
93
|
+
message: `Could not find Like button for post: ${postUrl}`,
|
|
94
|
+
details: 'Like button element not found, page may not have loaded or UI changed'
|
|
95
|
+
},
|
|
56
96
|
status: 'failed'
|
|
57
97
|
};
|
|
58
98
|
}
|
|
@@ -9,41 +9,70 @@ export async function executeSendConnectionRequest(page, action) {
|
|
|
9
9
|
|
|
10
10
|
try {
|
|
11
11
|
const profileUrl = `https://www.linkedin.com/in/${username}`;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
console.log('[SendConnectionRequest] Starting connection request for:', username);
|
|
13
|
+
console.log('[SendConnectionRequest] Navigating to profile:', profileUrl);
|
|
14
|
+
console.log('[SendConnectionRequest] Will wait up to 60 seconds for page load');
|
|
15
|
+
try {
|
|
16
|
+
await safeGoto(page, profileUrl, {
|
|
17
|
+
waitUntil: 'load',
|
|
18
|
+
timeout: 60000
|
|
19
|
+
});
|
|
20
|
+
console.log('[SendConnectionRequest] Profile page loaded successfully');
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('[SendConnectionRequest] Navigation error:', error.message);
|
|
23
|
+
if (error.message.includes('timeout') || error.message.includes('Timeout')) {
|
|
24
|
+
console.error('[SendConnectionRequest] Network timeout during profile navigation');
|
|
25
|
+
throw new Error(`Network timeout while navigating to profile: ${error.message}`);
|
|
26
|
+
}
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
16
29
|
|
|
17
30
|
await page.waitForLoadState('load');
|
|
18
31
|
await waitRandom(1700, 2600, page);
|
|
19
32
|
|
|
33
|
+
console.log('[SendConnectionRequest] Scrolling page and detecting connection status');
|
|
20
34
|
await page.evaluate(() => { window.scrollTo(0, 300); });
|
|
21
35
|
await waitRandom(600, 2500, page);
|
|
22
36
|
|
|
23
|
-
|
|
37
|
+
const result = await detectConnectionStatus(page, username);
|
|
38
|
+
console.log('[SendConnectionRequest] Connection status detected:', result.status);
|
|
39
|
+
return result;
|
|
24
40
|
|
|
25
41
|
} catch (error) {
|
|
26
|
-
console.error('[SendConnectionRequest]
|
|
42
|
+
console.error('[SendConnectionRequest] Fatal error:', error.message);
|
|
43
|
+
console.error('[SendConnectionRequest] Error stack:', error.stack);
|
|
44
|
+
if (error.message.includes('timeout') || error.message.includes('Timeout')) {
|
|
45
|
+
console.error('[SendConnectionRequest] Network timeout detected');
|
|
46
|
+
}
|
|
27
47
|
throw error;
|
|
28
48
|
}
|
|
29
49
|
}
|
|
30
50
|
|
|
31
51
|
async function detectConnectionStatus(page, username) {
|
|
52
|
+
console.log('[SendConnectionRequest] Starting connection status detection');
|
|
32
53
|
let inMain = true;
|
|
33
54
|
|
|
55
|
+
console.log('[SendConnectionRequest] Attempting to find and click Connect button');
|
|
34
56
|
const connectResult = await handleConnectButtonClick(page, username, inMain, true);
|
|
35
57
|
if (connectResult.status === 'success') {
|
|
58
|
+
console.log('[SendConnectionRequest] Connection request sent successfully');
|
|
36
59
|
return connectResult;
|
|
37
60
|
}
|
|
61
|
+
console.log('[SendConnectionRequest] Connect button not found or click failed, checking connection status');
|
|
38
62
|
|
|
63
|
+
console.log('[SendConnectionRequest] Checking for "More" button to access connection options');
|
|
39
64
|
let moreActionsSelectors = await getButtonByText(page, 'More', inMain);
|
|
40
65
|
if (!moreActionsSelectors || !(await moreActionsSelectors.isVisible())) {
|
|
66
|
+
console.log('[SendConnectionRequest] "More" button not found, trying "More actions"');
|
|
41
67
|
moreActionsSelectors = await getButtonByText(page, 'More actions', inMain);
|
|
42
68
|
}
|
|
43
69
|
if (moreActionsSelectors && (await moreActionsSelectors.isVisible())) {
|
|
70
|
+
console.log('[SendConnectionRequest] Found More button, clicking to access dropdown');
|
|
44
71
|
await clickWithRetries(page, moreActionsSelectors);
|
|
45
72
|
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 1000) + 500));
|
|
46
73
|
}
|
|
74
|
+
|
|
75
|
+
console.log('[SendConnectionRequest] Checking if already connected');
|
|
47
76
|
let removeConnectionButton = await getButtonByText(page, 'Remove Connection', inMain);
|
|
48
77
|
if (!removeConnectionButton) {
|
|
49
78
|
removeConnectionButton = await getButtonByText(page, 'Remove', inMain);
|
|
@@ -52,13 +81,18 @@ async function detectConnectionStatus(page, username) {
|
|
|
52
81
|
removeConnectionButton = await getButtonByText(page, 'Connected', inMain);
|
|
53
82
|
}
|
|
54
83
|
if (removeConnectionButton) {
|
|
84
|
+
console.log('[SendConnectionRequest] Already connected with', username);
|
|
55
85
|
return { status: 'already_connected', message: `Already connected with ${username}.` };
|
|
56
86
|
}
|
|
57
87
|
|
|
88
|
+
console.log('[SendConnectionRequest] Not connected, retrying Connect button click');
|
|
58
89
|
const connectResultWithNote = await handleConnectButtonClick(page, username, inMain, true);
|
|
59
90
|
if (connectResultWithNote.status === 'success') {
|
|
91
|
+
console.log('[SendConnectionRequest] Connection request sent successfully on retry');
|
|
60
92
|
return connectResultWithNote;
|
|
61
93
|
}
|
|
94
|
+
|
|
95
|
+
console.log('[SendConnectionRequest] Checking if connection request is pending');
|
|
62
96
|
let sentConnectionButton = await getButtonByText(page, 'Sent connection', inMain);
|
|
63
97
|
if (!sentConnectionButton) {
|
|
64
98
|
sentConnectionButton = await getButtonByText(page, 'Pending', inMain);
|
|
@@ -67,9 +101,11 @@ async function detectConnectionStatus(page, username) {
|
|
|
67
101
|
sentConnectionButton = await getButtonByText(page, 'Invitation sent', inMain);
|
|
68
102
|
}
|
|
69
103
|
if (sentConnectionButton) {
|
|
104
|
+
console.log('[SendConnectionRequest] Connection request already pending for', username);
|
|
70
105
|
return { status: 'pending', message: `Connection request already pending for ${username}.` };
|
|
71
106
|
}
|
|
72
107
|
|
|
108
|
+
console.error('[SendConnectionRequest] Could not find Connect button or determine connection status');
|
|
73
109
|
return {
|
|
74
110
|
status: 'not_found',
|
|
75
111
|
message: `Could not find Connect button for ${username}. Please analyze dom and decide what to do next.`
|
|
@@ -77,51 +113,73 @@ async function detectConnectionStatus(page, username) {
|
|
|
77
113
|
}
|
|
78
114
|
|
|
79
115
|
async function handleConnectButtonClick(page, username, inMain = true, handleSendWithoutNote = true) {
|
|
116
|
+
console.log('[SendConnectionRequest] Searching for Connect button');
|
|
80
117
|
let connectButton = await getButtonByText(page, 'Connect', inMain);
|
|
81
118
|
if (!connectButton || !(await connectButton.isVisible())) {
|
|
119
|
+
console.log('[SendConnectionRequest] Connect button not found on first attempt, retrying');
|
|
82
120
|
connectButton = await getButtonByText(page, 'Connect', inMain);
|
|
83
121
|
}
|
|
84
122
|
|
|
85
123
|
if (connectButton && (await connectButton.isVisible())) {
|
|
124
|
+
console.log('[SendConnectionRequest] Connect button found and visible, clicking');
|
|
86
125
|
try {
|
|
87
126
|
await clickWithRetries(page, connectButton);
|
|
127
|
+
console.log('[SendConnectionRequest] Connect button clicked successfully');
|
|
88
128
|
|
|
89
129
|
if (handleSendWithoutNote) {
|
|
90
130
|
await waitRandom(1700, 2600, page);
|
|
131
|
+
console.log('[SendConnectionRequest] Searching for "Send without a note" button');
|
|
91
132
|
|
|
92
133
|
let sendWithoutNoteButton = await getButtonByText(page, 'Send without a note', false);
|
|
93
134
|
if (!sendWithoutNoteButton) {
|
|
135
|
+
console.log('[SendConnectionRequest] Trying alternative text "without a note"');
|
|
94
136
|
sendWithoutNoteButton = await getButtonByText(page, 'without a note', false);
|
|
95
137
|
}
|
|
96
138
|
if (!sendWithoutNoteButton) {
|
|
139
|
+
console.log('[SendConnectionRequest] Trying generic "Send" button');
|
|
97
140
|
sendWithoutNoteButton = await getButtonByText(page, 'Send', false);
|
|
98
141
|
}
|
|
99
142
|
|
|
100
143
|
if (sendWithoutNoteButton && (await sendWithoutNoteButton.isVisible())) {
|
|
144
|
+
console.log('[SendConnectionRequest] Found send button, clicking');
|
|
101
145
|
try {
|
|
102
146
|
await clickWithRetries(page, sendWithoutNoteButton);
|
|
103
147
|
await waitRandom(1700, 2600, page);
|
|
148
|
+
console.log('[SendConnectionRequest] Send button clicked, connection request should be sent');
|
|
104
149
|
return { status: 'success', message: `Connection request sent successfully to ${username}` };
|
|
105
150
|
} catch (error) {
|
|
106
|
-
|
|
151
|
+
console.error('[SendConnectionRequest] Error clicking Send button:', error.message);
|
|
152
|
+
if (error.message.includes('timeout') || error.message.includes('Timeout')) {
|
|
153
|
+
console.error('[SendConnectionRequest] Network timeout while clicking Send button');
|
|
154
|
+
}
|
|
155
|
+
return { status: 'unclear', message: `Error clicking Send without a note button for ${username}: ${error.message}` };
|
|
107
156
|
}
|
|
108
157
|
} else {
|
|
158
|
+
console.log('[SendConnectionRequest] Send button not found, checking if modal is open');
|
|
109
159
|
const existingModal = await page.locator('[data-test-modal-id="send-invite-modal"]').isVisible();
|
|
110
160
|
if (existingModal) {
|
|
161
|
+
console.log('[SendConnectionRequest] Modal is open, assuming success');
|
|
111
162
|
return { status: 'success', message: `Modal already open for ${username}` };
|
|
112
163
|
} else {
|
|
164
|
+
console.warn('[SendConnectionRequest] No send button found and modal not open');
|
|
113
165
|
return { status: 'unclear', message: `No send without a note button found for ${username}` };
|
|
114
166
|
}
|
|
115
167
|
}
|
|
116
168
|
} else {
|
|
169
|
+
console.log('[SendConnectionRequest] Not handling send without note, waiting for auto-send');
|
|
117
170
|
await waitRandom(4200, 6200, page);
|
|
118
171
|
return { status: 'success', message: `Connection request sent successfully to ${username}` };
|
|
119
172
|
}
|
|
120
173
|
} catch (error) {
|
|
174
|
+
console.error('[SendConnectionRequest] Error clicking Connect button:', error.message);
|
|
175
|
+
if (error.message.includes('timeout') || error.message.includes('Timeout')) {
|
|
176
|
+
console.error('[SendConnectionRequest] Network timeout while clicking Connect button');
|
|
177
|
+
}
|
|
121
178
|
return { status: 'error', message: `Error clicking Connect button for ${username}: ${error.message}` };
|
|
122
179
|
}
|
|
123
180
|
}
|
|
124
181
|
|
|
182
|
+
console.warn('[SendConnectionRequest] Connect button not found or not visible');
|
|
125
183
|
return { status: 'not_found', message: `Connect button not found for ${username}` };
|
|
126
184
|
}
|
|
127
185
|
|
package/tools/send_messages.js
CHANGED
|
@@ -19,12 +19,22 @@ export async function executeSendMessages(page, action) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
const profileUrl = `https://www.linkedin.com/in/${username}`;
|
|
22
|
-
console.log('[send_messages]
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
console.log('[send_messages] Starting navigation to profile:', profileUrl);
|
|
23
|
+
console.log('[send_messages] Will wait up to 60 seconds for page load');
|
|
24
|
+
try {
|
|
25
|
+
await safeGoto(page, profileUrl, {
|
|
26
|
+
waitUntil: 'load',
|
|
27
|
+
timeout: Math.floor(randomBetween(40000, 60000))
|
|
28
|
+
});
|
|
29
|
+
console.log('[send_messages] Page loaded successfully, current URL:', page.url());
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error('[send_messages] Navigation error:', error.message);
|
|
32
|
+
if (error.message.includes('timeout') || error.message.includes('Timeout')) {
|
|
33
|
+
console.error('[send_messages] Network timeout during page navigation');
|
|
34
|
+
throw new Error(`Network timeout while navigating to profile: ${error.message}`);
|
|
35
|
+
}
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
28
38
|
|
|
29
39
|
await page.waitForLoadState('domcontentloaded');
|
|
30
40
|
await waitRandom(800, 1400, page);
|
|
@@ -178,12 +188,12 @@ export async function executeSendMessages(page, action) {
|
|
|
178
188
|
|
|
179
189
|
let editorReady = false;
|
|
180
190
|
for (let attempt = 0; attempt < 5; attempt++) {
|
|
181
|
-
console.log('[send_messages] Editor
|
|
191
|
+
console.log('[send_messages] Editor search attempt', attempt + 1, 'of 5');
|
|
182
192
|
try {
|
|
183
193
|
const isVisible = await editor.isVisible({ timeout: 2000 }).catch(() => false);
|
|
184
194
|
console.log('[send_messages] Editor visible:', isVisible);
|
|
185
195
|
if (isVisible) {
|
|
186
|
-
console.log('[send_messages]
|
|
196
|
+
console.log('[send_messages] Editor found, clicking to focus');
|
|
187
197
|
await humanLikeClick(page, editor, { timeout: 3000, fallback: { force: true, timeout: 3000 } });
|
|
188
198
|
await waitRandom(180, 260, page);
|
|
189
199
|
|
|
@@ -203,23 +213,34 @@ export async function executeSendMessages(page, action) {
|
|
|
203
213
|
|
|
204
214
|
if (isFocused) {
|
|
205
215
|
editorReady = true;
|
|
206
|
-
console.log('[send_messages] Editor ready!');
|
|
216
|
+
console.log('[send_messages] Editor ready and focused!');
|
|
207
217
|
break;
|
|
218
|
+
} else {
|
|
219
|
+
console.warn('[send_messages] Editor not focused after click, will retry');
|
|
208
220
|
}
|
|
221
|
+
} else {
|
|
222
|
+
console.log('[send_messages] Editor not visible, will retry');
|
|
209
223
|
}
|
|
210
224
|
} catch (e) {
|
|
211
|
-
console.
|
|
225
|
+
console.warn('[send_messages] Error preparing editor on attempt', attempt + 1, '-', e.message);
|
|
226
|
+
if (e.message.includes('timeout') || e.message.includes('Timeout')) {
|
|
227
|
+
console.warn('[send_messages] Network timeout while searching for editor');
|
|
228
|
+
}
|
|
212
229
|
}
|
|
213
230
|
|
|
214
231
|
await waitRandom(260, 420, page);
|
|
215
|
-
|
|
216
|
-
|
|
232
|
+
if (!editorReady) {
|
|
233
|
+
console.log('[send_messages] Retrying to find message input');
|
|
234
|
+
await findAndClickMessageInput(page);
|
|
235
|
+
}
|
|
217
236
|
}
|
|
218
237
|
|
|
219
238
|
if (!editorReady) {
|
|
220
|
-
console.warn('[send_messages] Editor not ready after 5 attempts, trying
|
|
239
|
+
console.warn('[send_messages] Editor not ready after 5 attempts, trying alternative method');
|
|
240
|
+
console.warn('[send_messages] Attempting to find and click message input using alternative selector');
|
|
221
241
|
await findAndClickMessageInput(page);
|
|
222
242
|
await waitRandom(420, 620, page);
|
|
243
|
+
console.log('[send_messages] Alternative method completed, checking if editor is now available');
|
|
223
244
|
}
|
|
224
245
|
|
|
225
246
|
console.log('[send_messages] Typing message:', message);
|
|
@@ -257,22 +278,30 @@ export async function executeSendMessages(page, action) {
|
|
|
257
278
|
console.log('[send_messages] Clicking send button');
|
|
258
279
|
const clickedSend = await clickSendButton(page);
|
|
259
280
|
if (!clickedSend) {
|
|
260
|
-
console.error('[send_messages] Failed to click send button');
|
|
281
|
+
console.error('[send_messages] Failed to click send button after all attempts');
|
|
282
|
+
console.error('[send_messages] Message was typed but send button could not be found or clicked');
|
|
283
|
+
console.error('[send_messages] This may indicate a network timeout, page load issue, or UI change');
|
|
261
284
|
return {
|
|
262
285
|
action: 'send_messages',
|
|
263
|
-
result: {
|
|
286
|
+
result: {
|
|
287
|
+
status: 'send_button_not_found',
|
|
288
|
+
message: 'Could not find send button. Message may have been typed but not sent.',
|
|
289
|
+
details: 'Button search timed out or button not found after message was typed'
|
|
290
|
+
},
|
|
264
291
|
status: 'failed'
|
|
265
292
|
};
|
|
266
293
|
}
|
|
267
294
|
console.log('[send_messages] Send button clicked successfully');
|
|
295
|
+
console.log('[send_messages] Waiting for message to be sent...');
|
|
268
296
|
|
|
269
297
|
await waitRandom(520, 1500, page);
|
|
298
|
+
console.log('[send_messages] Wait completed after send button click');
|
|
270
299
|
}
|
|
271
300
|
|
|
272
|
-
console.log('[send_messages]
|
|
301
|
+
console.log('[send_messages] All messages processed, closing conversation bubbles');
|
|
273
302
|
await closeAllConversationBubbles(page);
|
|
274
303
|
|
|
275
|
-
console.log('[send_messages] Successfully completed
|
|
304
|
+
console.log('[send_messages] Successfully completed sending all messages to', username);
|
|
276
305
|
return {
|
|
277
306
|
action: 'send_messages',
|
|
278
307
|
result: { status: 'success', message: `Message successfully sent to ${username}` },
|
|
@@ -331,23 +360,34 @@ async function closeAllConversationBubbles(page) {
|
|
|
331
360
|
}
|
|
332
361
|
|
|
333
362
|
async function clickSendButton(page) {
|
|
334
|
-
console.log('[send_messages] clickSendButton:
|
|
363
|
+
console.log('[send_messages] clickSendButton: Starting button search, waiting 500ms');
|
|
335
364
|
await waitRandom(420, 620, page);
|
|
336
365
|
|
|
337
366
|
for (let attempt = 0; attempt < 10; attempt++) {
|
|
338
|
-
console.log('[send_messages] clickSendButton: Attempt', attempt + 1, 'of 10');
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
367
|
+
console.log('[send_messages] clickSendButton: Attempt', attempt + 1, 'of 10 - searching for send button');
|
|
368
|
+
try {
|
|
369
|
+
const button = page.locator('.msg-form__send-button:not([disabled])').first();
|
|
370
|
+
const isVisible = await button.isVisible({ timeout: 3000 }).catch(() => false);
|
|
371
|
+
if (isVisible) {
|
|
372
|
+
console.log('[send_messages] clickSendButton: Send button found and visible, attempting click');
|
|
373
|
+
await humanLikeClick(page, button, { timeout: 3000 });
|
|
374
|
+
console.log('[send_messages] clickSendButton: Send button clicked successfully');
|
|
375
|
+
return true;
|
|
376
|
+
} else {
|
|
377
|
+
console.log('[send_messages] clickSendButton: Send button not visible, waiting before retry');
|
|
378
|
+
}
|
|
379
|
+
} catch (error) {
|
|
380
|
+
console.warn('[send_messages] clickSendButton: Error during attempt', attempt + 1, '-', error.message);
|
|
381
|
+
if (error.message.includes('timeout') || error.message.includes('Timeout')) {
|
|
382
|
+
console.warn('[send_messages] clickSendButton: Network timeout detected during button search');
|
|
383
|
+
}
|
|
345
384
|
}
|
|
346
385
|
|
|
347
386
|
await waitRandom(260, 420, page);
|
|
348
387
|
}
|
|
349
388
|
|
|
350
|
-
console.error('[send_messages] Failed to click send button after 10 attempts');
|
|
389
|
+
console.error('[send_messages] clickSendButton: Failed to click send button after 10 attempts');
|
|
390
|
+
console.error('[send_messages] clickSendButton: Possible causes: network timeout, button not found, page not loaded, or UI changed');
|
|
351
391
|
return false;
|
|
352
392
|
}
|
|
353
393
|
|