coffeeinabit 0.0.6 → 0.0.7
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 +38 -23
- package/package.json +1 -1
- package/tools/get_daily_linkedin_connections.js +148 -33
- package/tools/get_linkedin_search_results.js +56 -56
- package/tools/get_messages.js +297 -31
package/linkedin_automation.js
CHANGED
|
@@ -44,6 +44,7 @@ export class LinkedInAutomation {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
async saveLinkedInSession() {
|
|
47
|
+
console.warn('[LinkedInAutomation] saveLinkedInSession is deprecated - using persistent context instead');
|
|
47
48
|
try {
|
|
48
49
|
if (!this.context || !this.page) {
|
|
49
50
|
console.error('[LinkedInAutomation] Cannot save session - no context or page');
|
|
@@ -91,6 +92,7 @@ export class LinkedInAutomation {
|
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
async loadLinkedInSession() {
|
|
95
|
+
console.warn('[LinkedInAutomation] loadLinkedInSession is deprecated - using persistent context instead');
|
|
94
96
|
try {
|
|
95
97
|
const sessionPath = this.getLinkedInSessionPath();
|
|
96
98
|
|
|
@@ -130,6 +132,7 @@ export class LinkedInAutomation {
|
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
async restoreLocalStorage() {
|
|
135
|
+
console.warn('[LinkedInAutomation] restoreLocalStorage is deprecated - using persistent context instead');
|
|
133
136
|
try {
|
|
134
137
|
const sessionPath = this.getLinkedInSessionPath();
|
|
135
138
|
|
|
@@ -322,8 +325,7 @@ export class LinkedInAutomation {
|
|
|
322
325
|
if (secondCheck.includes('/feed')) {
|
|
323
326
|
console.log('[LinkedInAutomation] Second check confirmed - still on /feed/');
|
|
324
327
|
|
|
325
|
-
console.log('[LinkedInAutomation]
|
|
326
|
-
await this.saveLinkedInSession();
|
|
328
|
+
console.log('[LinkedInAutomation] Session will be persisted automatically by launchPersistentContext...');
|
|
327
329
|
|
|
328
330
|
this.status = 'linkedin_logged_in';
|
|
329
331
|
this.emitStatus();
|
|
@@ -384,10 +386,6 @@ export class LinkedInAutomation {
|
|
|
384
386
|
if (this.context) {
|
|
385
387
|
await this.context.close();
|
|
386
388
|
this.context = null;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (this.browser) {
|
|
390
|
-
await this.browser.close();
|
|
391
389
|
this.browser = null;
|
|
392
390
|
}
|
|
393
391
|
|
|
@@ -696,25 +694,40 @@ export class LinkedInAutomation {
|
|
|
696
694
|
}
|
|
697
695
|
|
|
698
696
|
async launchBrowserAndLogin() {
|
|
699
|
-
console.log('[LinkedInAutomation] Launching Firefox for LinkedIn login...');
|
|
697
|
+
console.log('[LinkedInAutomation] Launching Firefox with persistent context for LinkedIn login...');
|
|
698
|
+
|
|
699
|
+
this.userEmail = this._currentUserEmail || 'default_user';
|
|
700
|
+
const sanitizedEmail = this.userEmail.replace(/[^a-zA-Z0-9]/g, '_');
|
|
701
|
+
const userDataPath = path.join(this.linkedInSessionDir, `linkedin_auth_${sanitizedEmail}`);
|
|
700
702
|
|
|
701
|
-
|
|
703
|
+
console.log('[LinkedInAutomation] User data path:', userDataPath);
|
|
702
704
|
|
|
703
|
-
|
|
705
|
+
if (!fs.existsSync(userDataPath)) {
|
|
706
|
+
fs.mkdirSync(userDataPath, { recursive: true });
|
|
707
|
+
console.log('[LinkedInAutomation] Created user data directory:', userDataPath);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const launchOptions = {
|
|
711
|
+
headless: this.headless || false,
|
|
704
712
|
viewport: null,
|
|
705
713
|
ignoreHTTPSErrors: true
|
|
706
|
-
}
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
this.context = await firefox.launchPersistentContext(userDataPath, launchOptions);
|
|
707
717
|
|
|
718
|
+
console.log('[LinkedInAutomation] Persistent context launched successfully');
|
|
719
|
+
|
|
720
|
+
this.browser = this.context;
|
|
721
|
+
|
|
708
722
|
await this.enablePerformanceMode();
|
|
709
723
|
this.page = await this.context.newPage();
|
|
710
724
|
|
|
711
|
-
|
|
725
|
+
console.log('[LinkedInAutomation] Checking if user is already authenticated...');
|
|
712
726
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
console.log('[LinkedInAutomation] No saved session, starting fresh login...');
|
|
727
|
+
const isAuthenticated = await this.checkAndVerifySession();
|
|
728
|
+
|
|
729
|
+
if (!isAuthenticated) {
|
|
730
|
+
console.log('[LinkedInAutomation] Not authenticated, starting fresh login...');
|
|
718
731
|
await this.startLinkedInLogin();
|
|
719
732
|
}
|
|
720
733
|
}
|
|
@@ -737,17 +750,21 @@ export class LinkedInAutomation {
|
|
|
737
750
|
|
|
738
751
|
const currentUrl = this.page.url();
|
|
739
752
|
const currentTitle = await this.page.title();
|
|
753
|
+
const sanitizedEmail = this.userEmail.replace(/[^a-zA-Z0-9]/g, '_');
|
|
754
|
+
const userDataPath = path.join(this.linkedInSessionDir, `linkedin_auth_${sanitizedEmail}`);
|
|
740
755
|
|
|
741
|
-
await this.
|
|
756
|
+
await this.context.close();
|
|
742
757
|
|
|
743
758
|
this.headless = newHeadless;
|
|
744
|
-
this.browser = await firefox.launch({ headless: this.headless });
|
|
745
759
|
|
|
746
|
-
this.context = await
|
|
760
|
+
this.context = await firefox.launchPersistentContext(userDataPath, {
|
|
761
|
+
headless: this.headless,
|
|
747
762
|
viewport: null,
|
|
748
763
|
ignoreHTTPSErrors: true
|
|
749
764
|
});
|
|
750
765
|
|
|
766
|
+
this.browser = this.context;
|
|
767
|
+
|
|
751
768
|
await this.enablePerformanceMode();
|
|
752
769
|
this.page = await this.context.newPage();
|
|
753
770
|
|
|
@@ -773,14 +790,12 @@ export class LinkedInAutomation {
|
|
|
773
790
|
|
|
774
791
|
async checkAndVerifySession() {
|
|
775
792
|
try {
|
|
776
|
-
console.log('[LinkedInAutomation] Navigating to LinkedIn feed to verify session...');
|
|
793
|
+
console.log('[LinkedInAutomation] Navigating to LinkedIn feed to verify persistent session...');
|
|
777
794
|
await this.page.goto('https://www.linkedin.com/feed/', {
|
|
778
795
|
waitUntil: 'domcontentloaded',
|
|
779
796
|
timeout: 30000
|
|
780
797
|
});
|
|
781
798
|
|
|
782
|
-
await this.restoreLocalStorage();
|
|
783
|
-
|
|
784
799
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
785
800
|
|
|
786
801
|
let verifyAttempts = 0;
|
|
@@ -791,7 +806,7 @@ export class LinkedInAutomation {
|
|
|
791
806
|
console.log(`[LinkedInAutomation] Verification attempt ${verifyAttempts + 1}/${maxVerifyAttempts}, URL: ${this.currentUrl}`);
|
|
792
807
|
|
|
793
808
|
if (this.currentUrl.includes('/feed')) {
|
|
794
|
-
console.log('[LinkedInAutomation] Session verified! Already logged in.');
|
|
809
|
+
console.log('[LinkedInAutomation] Session verified! Already logged in via persistent context.');
|
|
795
810
|
this.status = 'linkedin_logged_in';
|
|
796
811
|
this.emitStatus();
|
|
797
812
|
|
package/package.json
CHANGED
|
@@ -25,38 +25,43 @@ export async function executeGetDailyLinkedInConnections(page, action) {
|
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
const now = Date.now();
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return null;
|
|
28
|
+
let allConnectionsMap = new Map();
|
|
29
|
+
let shouldStopScrolling = false;
|
|
30
|
+
let noScrollAttempts = 0;
|
|
31
|
+
const maxNoScrollAttempts = 2;
|
|
32
|
+
|
|
33
|
+
while (!shouldStopScrolling) {
|
|
34
|
+
const currentConnections = await page.evaluate(() => {
|
|
35
|
+
function parseConnectionDate(str) {
|
|
36
|
+
if (!str) return null;
|
|
37
|
+
const match = str.match(/Connected on (.+)/i);
|
|
38
|
+
if (!match) return null;
|
|
39
|
+
const dateStr = match[1].trim();
|
|
40
|
+
try {
|
|
41
|
+
const timestamp = new Date(dateStr).getTime();
|
|
42
|
+
return isNaN(timestamp) ? null : timestamp;
|
|
43
|
+
} catch (e) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
39
46
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (!lastCheckTimestamp || (connectionTimestamp && connectionTimestamp >= lastCheckTimestamp)) {
|
|
47
|
+
|
|
48
|
+
const connections = [];
|
|
49
|
+
const allParagraphs = document.querySelectorAll('p');
|
|
50
|
+
|
|
51
|
+
allParagraphs.forEach(p => {
|
|
52
|
+
const text = p.textContent.trim();
|
|
53
|
+
if (text.startsWith('Connected on')) {
|
|
54
|
+
const parentDiv = p.parentElement;
|
|
55
|
+
if (parentDiv) {
|
|
56
|
+
const profileLink = parentDiv.querySelector('a[href*="/in/"]');
|
|
57
|
+
if (profileLink) {
|
|
58
|
+
const href = profileLink.getAttribute('href');
|
|
59
|
+
const usernameMatch = href.match(/\/in\/([^/?]+)/);
|
|
60
|
+
if (usernameMatch) {
|
|
61
|
+
const username = usernameMatch[1];
|
|
62
|
+
const connectionDate = text;
|
|
63
|
+
const connectionTimestamp = parseConnectionDate(connectionDate);
|
|
64
|
+
|
|
60
65
|
connections.push({
|
|
61
66
|
username: username,
|
|
62
67
|
connectionDate: connectionDate,
|
|
@@ -66,11 +71,121 @@ export async function executeGetDailyLinkedInConnections(page, action) {
|
|
|
66
71
|
}
|
|
67
72
|
}
|
|
68
73
|
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return connections;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
currentConnections.forEach(conn => {
|
|
80
|
+
allConnectionsMap.set(conn.username, conn);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
console.log(`[get_daily_linkedin_connections] Current page connections: ${currentConnections.length}, Total unique: ${allConnectionsMap.size}`);
|
|
84
|
+
|
|
85
|
+
if (lastCheckTimestamp && currentConnections.length > 0) {
|
|
86
|
+
const oldestConnection = currentConnections.reduce((oldest, current) => {
|
|
87
|
+
if (!oldest.connectionTimestamp) return current;
|
|
88
|
+
if (!current.connectionTimestamp) return oldest;
|
|
89
|
+
return current.connectionTimestamp < oldest.connectionTimestamp ? current : oldest;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (oldestConnection.connectionTimestamp && oldestConnection.connectionTimestamp < lastCheckTimestamp) {
|
|
93
|
+
console.log('[get_daily_linkedin_connections] Reached connections older than last check, stopping');
|
|
94
|
+
shouldStopScrolling = true;
|
|
95
|
+
break;
|
|
69
96
|
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const scrollInfoBefore = await page.evaluate(() => {
|
|
100
|
+
const main = document.querySelector('main');
|
|
101
|
+
if (main) {
|
|
102
|
+
return {
|
|
103
|
+
scrollTop: main.scrollTop,
|
|
104
|
+
scrollHeight: main.scrollHeight,
|
|
105
|
+
clientHeight: main.clientHeight
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
scrollTop: window.pageYOffset || document.documentElement.scrollTop,
|
|
110
|
+
scrollHeight: document.documentElement.scrollHeight,
|
|
111
|
+
clientHeight: window.innerHeight
|
|
112
|
+
};
|
|
70
113
|
});
|
|
71
114
|
|
|
72
|
-
|
|
73
|
-
|
|
115
|
+
console.log(`[get_daily_linkedin_connections] Before scroll - Top: ${scrollInfoBefore.scrollTop}px, Height: ${scrollInfoBefore.scrollHeight}px`);
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
console.log('[get_daily_linkedin_connections] Scrolling main element...');
|
|
119
|
+
|
|
120
|
+
const mainElement = await page.locator('main').first();
|
|
121
|
+
const containerExists = await mainElement.count();
|
|
122
|
+
|
|
123
|
+
if (containerExists > 0) {
|
|
124
|
+
await mainElement.evaluate(el => {
|
|
125
|
+
console.log('Main element found, scrolling...');
|
|
126
|
+
console.log('Main scrollHeight:', el.scrollHeight);
|
|
127
|
+
console.log('Main clientHeight:', el.clientHeight);
|
|
128
|
+
console.log('Main scrollTop before:', el.scrollTop);
|
|
129
|
+
|
|
130
|
+
el.scrollBy({ top: 2000, behavior: 'smooth' });
|
|
131
|
+
|
|
132
|
+
console.log('Main scrollTop after:', el.scrollTop);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
console.log('[get_daily_linkedin_connections] Scroll completed');
|
|
136
|
+
} else {
|
|
137
|
+
console.log('[get_daily_linkedin_connections] Main element not found');
|
|
138
|
+
}
|
|
139
|
+
console.log('[get_daily_linkedin_connections] Waiting for content to load...');
|
|
140
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
141
|
+
} catch (e) {
|
|
142
|
+
console.log(`[get_daily_linkedin_connections] Scroll failed: ${e.message}`);
|
|
143
|
+
console.log('Stack trace:', e.stack);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const scrollInfoAfter = await page.evaluate(() => {
|
|
147
|
+
const main = document.querySelector('main');
|
|
148
|
+
if (main) {
|
|
149
|
+
return {
|
|
150
|
+
scrollTop: main.scrollTop,
|
|
151
|
+
scrollHeight: main.scrollHeight,
|
|
152
|
+
clientHeight: main.clientHeight
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
scrollTop: window.pageYOffset || document.documentElement.scrollTop,
|
|
157
|
+
scrollHeight: document.documentElement.scrollHeight,
|
|
158
|
+
clientHeight: window.innerHeight
|
|
159
|
+
};
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
console.log(`[get_daily_linkedin_connections] After scroll - Top: ${scrollInfoAfter.scrollTop}px, Height: ${scrollInfoAfter.scrollHeight}px`);
|
|
163
|
+
|
|
164
|
+
const scrolled = scrollInfoAfter.scrollTop > scrollInfoBefore.scrollTop;
|
|
165
|
+
const heightIncreased = scrollInfoAfter.scrollHeight > scrollInfoBefore.scrollHeight;
|
|
166
|
+
|
|
167
|
+
if (scrolled || heightIncreased) {
|
|
168
|
+
console.log('[get_daily_linkedin_connections] Page scrolled or expanded, continuing...');
|
|
169
|
+
noScrollAttempts = 0;
|
|
170
|
+
} else {
|
|
171
|
+
noScrollAttempts++;
|
|
172
|
+
console.log(`[get_daily_linkedin_connections] No scroll detected (attempt ${noScrollAttempts}/${maxNoScrollAttempts})`);
|
|
173
|
+
|
|
174
|
+
if (noScrollAttempts < maxNoScrollAttempts) {
|
|
175
|
+
console.log('[get_daily_linkedin_connections] Waiting 5 seconds and trying again...');
|
|
176
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
177
|
+
} else {
|
|
178
|
+
console.log('[get_daily_linkedin_connections] Max attempts reached, stopping scroll');
|
|
179
|
+
shouldStopScrolling = true;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const allConnections = Array.from(allConnectionsMap.values());
|
|
185
|
+
|
|
186
|
+
const newConnections = allConnections.filter(conn => {
|
|
187
|
+
return !lastCheckTimestamp || (conn.connectionTimestamp && conn.connectionTimestamp >= lastCheckTimestamp);
|
|
188
|
+
});
|
|
74
189
|
|
|
75
190
|
return {
|
|
76
191
|
action: 'get_daily_linkedin_connections',
|
|
@@ -38,19 +38,15 @@ export async function executeLinkedInSearch(page, action) {
|
|
|
38
38
|
|
|
39
39
|
try {
|
|
40
40
|
title = await page.title();
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
break;
|
|
50
|
-
}
|
|
51
|
-
|
|
41
|
+
|
|
42
|
+
// LinkedIn’s new structure
|
|
43
|
+
const htmlContent = await page.content();
|
|
44
|
+
|
|
45
|
+
// Look for cards directly by anchor structure
|
|
46
|
+
const hasCards = await page.locator('a[href*="/in/"]').count();
|
|
47
|
+
console.log(`[LinkedInSearch] Found ${hasCards} profile cards`);
|
|
48
|
+
|
|
52
49
|
const pageSearchResults = await parseLinkedInSearchResultsFromHTML(htmlContent);
|
|
53
|
-
|
|
54
50
|
pageResults = pageSearchResults;
|
|
55
51
|
} catch (error) {
|
|
56
52
|
pageResults = [];
|
|
@@ -65,7 +61,6 @@ export async function executeLinkedInSearch(page, action) {
|
|
|
65
61
|
|
|
66
62
|
try {
|
|
67
63
|
await page.goto(nextUrl, {
|
|
68
|
-
waitUntil: 'load',
|
|
69
64
|
timeout: Math.floor(Math.random() * 20000) + 10000
|
|
70
65
|
});
|
|
71
66
|
navigationSuccess = true;
|
|
@@ -90,52 +85,57 @@ export async function executeLinkedInSearch(page, action) {
|
|
|
90
85
|
}
|
|
91
86
|
|
|
92
87
|
async function parseLinkedInSearchResultsFromHTML(htmlContent) {
|
|
88
|
+
const results = [];
|
|
93
89
|
try {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
90
|
+
const cardPattern = /<a[^>]+href="([^"]*linkedin\.com\/in\/[^"]+)"[^>]*>([\s\S]*?)<\/a>/gi;
|
|
91
|
+
|
|
92
|
+
let match;
|
|
93
|
+
const seen = new Set();
|
|
94
|
+
|
|
95
|
+
while ((match = cardPattern.exec(htmlContent)) !== null) {
|
|
96
|
+
const [fullMatch, href, contentHtml] = match;
|
|
97
|
+
if (seen.has(href)) continue; // avoid duplicates
|
|
98
|
+
seen.add(href);
|
|
99
|
+
|
|
100
|
+
const usernameMatch = href.match(/\/in\/([^/?#]+)/);
|
|
101
|
+
const username = usernameMatch ? usernameMatch[1] : "";
|
|
102
|
+
|
|
103
|
+
// Extract name
|
|
104
|
+
const nameMatch = contentHtml.match(/>([^<>]{2,50})<\/span>/);
|
|
105
|
+
const name = nameMatch ? nameMatch[1].trim() : "";
|
|
106
|
+
|
|
107
|
+
// Extract headline (title, role)
|
|
108
|
+
const headlineMatch = contentHtml.match(/<div[^>]*>\s*([^<]{5,100})<\/div>/);
|
|
109
|
+
const headline = headlineMatch ? headlineMatch[1].trim() : "";
|
|
110
|
+
|
|
111
|
+
// Extract connection level
|
|
112
|
+
const connectionMatch = contentHtml.match(/\b(1st|2nd|3rd\+)\b/);
|
|
113
|
+
const connectionLevel = connectionMatch ? connectionMatch[1] : "";
|
|
114
|
+
|
|
115
|
+
// Extract location
|
|
116
|
+
const locationMatch = htmlContent
|
|
117
|
+
.slice(cardPattern.lastIndex, cardPattern.lastIndex + 800)
|
|
118
|
+
.match(/([A-Z][a-z]+(?:, [A-Z]{2})?)(?=<)/);
|
|
119
|
+
const location = locationMatch ? locationMatch[1].trim() : "";
|
|
120
|
+
|
|
121
|
+
// Unique ID (message ID-like)
|
|
122
|
+
const uniqueIdMatch = fullMatch.match(/data-view-tracking-scope=.*?contentTrackingId":"([^&]+)"/);
|
|
123
|
+
const uniqueId = uniqueIdMatch ? uniqueIdMatch[1] : "";
|
|
124
|
+
|
|
125
|
+
results.push({
|
|
126
|
+
username,
|
|
127
|
+
link: href,
|
|
128
|
+
name,
|
|
129
|
+
headline,
|
|
130
|
+
location,
|
|
131
|
+
connectionLevel,
|
|
132
|
+
uniqueId,
|
|
133
|
+
});
|
|
133
134
|
}
|
|
134
|
-
|
|
135
|
+
|
|
135
136
|
return results;
|
|
136
|
-
|
|
137
137
|
} catch (error) {
|
|
138
|
-
console.error(
|
|
138
|
+
console.error("[LinkedInSearch] Parsing failed:", error);
|
|
139
139
|
return [];
|
|
140
140
|
}
|
|
141
|
-
}
|
|
141
|
+
}
|
package/tools/get_messages.js
CHANGED
|
@@ -12,6 +12,23 @@ export async function executeGetMessages(page, action) {
|
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
await page.waitForLoadState('domcontentloaded');
|
|
15
|
+
await page.waitForLoadState('networkidle');
|
|
16
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
17
|
+
|
|
18
|
+
await page.evaluate(() => { window.scrollTo(0, 0); });
|
|
19
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
20
|
+
await page.evaluate(() => { window.scrollTo(0, 300); });
|
|
21
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
await page.waitForSelector('button[aria-label^="Message"], main button.artdeco-button--primary', {
|
|
25
|
+
timeout: 5000,
|
|
26
|
+
state: 'attached'
|
|
27
|
+
});
|
|
28
|
+
} catch (error) {
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
15
32
|
|
|
16
33
|
const pendingButton = await getButtonByText(page, 'Pending', true);
|
|
17
34
|
if (pendingButton) {
|
|
@@ -22,20 +39,204 @@ export async function executeGetMessages(page, action) {
|
|
|
22
39
|
};
|
|
23
40
|
}
|
|
24
41
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
42
|
+
let clicked = false;
|
|
43
|
+
|
|
44
|
+
const checkOverlayAppeared = async (page) => {
|
|
45
|
+
const overlaySelectors = [
|
|
46
|
+
'.msg-overlay-conversation-bubble__content-wrapper',
|
|
47
|
+
'.msg-overlay',
|
|
48
|
+
'[class*="msg-overlay"]',
|
|
49
|
+
'[class*="conversation-bubble"]'
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
for (let attempt = 0; attempt < 15; attempt++) {
|
|
53
|
+
for (const selector of overlaySelectors) {
|
|
54
|
+
try {
|
|
55
|
+
const count = await page.locator(selector).count();
|
|
56
|
+
if (count > 0) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
await page.waitForSelector('.msg-overlay-conversation-bubble__content-wrapper', {
|
|
67
|
+
timeout: 5000,
|
|
68
|
+
state: 'attached'
|
|
69
|
+
});
|
|
33
70
|
return true;
|
|
71
|
+
} catch (error) {
|
|
34
72
|
}
|
|
35
|
-
|
|
73
|
+
|
|
36
74
|
return false;
|
|
37
|
-
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const clickStrategies = [
|
|
78
|
+
async () => {
|
|
79
|
+
try {
|
|
80
|
+
const buttons = await page.locator('button[aria-label^="Message"]').all();
|
|
81
|
+
for (const btn of buttons) {
|
|
82
|
+
const isVisible = await btn.isVisible().catch(() => false);
|
|
83
|
+
const ariaLabel = await btn.getAttribute('aria-label').catch(() => '');
|
|
84
|
+
if (isVisible && ariaLabel && ariaLabel.startsWith('Message')) {
|
|
85
|
+
await btn.scrollIntoViewIfNeeded();
|
|
86
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
87
|
+
await btn.click({ timeout: 5000, force: false });
|
|
88
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
89
|
+
return await checkOverlayAppeared(page);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
},
|
|
96
|
+
async () => {
|
|
97
|
+
try {
|
|
98
|
+
const buttons = await page.locator('main button[aria-label^="Message"]').all();
|
|
99
|
+
for (const btn of buttons) {
|
|
100
|
+
const isVisible = await btn.isVisible().catch(() => false);
|
|
101
|
+
if (isVisible) {
|
|
102
|
+
await btn.scrollIntoViewIfNeeded();
|
|
103
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
104
|
+
await btn.click({ timeout: 5000, force: false });
|
|
105
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
106
|
+
return await checkOverlayAppeared(page);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
},
|
|
113
|
+
async () => {
|
|
114
|
+
try {
|
|
115
|
+
const buttons = await page.locator('main button.artdeco-button--primary').all();
|
|
116
|
+
for (const btn of buttons) {
|
|
117
|
+
try {
|
|
118
|
+
const text = await btn.textContent().catch(() => '');
|
|
119
|
+
const ariaLabel = await btn.getAttribute('aria-label').catch(() => '');
|
|
120
|
+
const isVisible = await btn.isVisible().catch(() => false);
|
|
121
|
+
|
|
122
|
+
if (isVisible && text && text.trim().includes('Message') &&
|
|
123
|
+
(!text.includes('Pending')) &&
|
|
124
|
+
(!ariaLabel || !ariaLabel.includes('Pending'))) {
|
|
125
|
+
await btn.scrollIntoViewIfNeeded();
|
|
126
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
127
|
+
await btn.click({ timeout: 5000, force: false });
|
|
128
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
129
|
+
return await checkOverlayAppeared(page);
|
|
130
|
+
}
|
|
131
|
+
} catch (error) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
} catch (error) {
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
},
|
|
139
|
+
async () => {
|
|
140
|
+
try {
|
|
141
|
+
const allButtons = await page.locator('main button').all();
|
|
142
|
+
for (const btn of allButtons) {
|
|
143
|
+
try {
|
|
144
|
+
const text = await btn.textContent().catch(() => '');
|
|
145
|
+
const ariaLabel = await btn.getAttribute('aria-label').catch(() => '');
|
|
146
|
+
const isVisible = await btn.isVisible().catch(() => false);
|
|
147
|
+
|
|
148
|
+
if (isVisible && ((ariaLabel && ariaLabel.startsWith('Message')) ||
|
|
149
|
+
(text && text.includes('Message') && !text.includes('Pending')))) {
|
|
150
|
+
await btn.scrollIntoViewIfNeeded();
|
|
151
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
152
|
+
await btn.click({ timeout: 5000, force: false });
|
|
153
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
154
|
+
return await checkOverlayAppeared(page);
|
|
155
|
+
}
|
|
156
|
+
} catch (error) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
} catch (error) {
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
},
|
|
164
|
+
async () => {
|
|
165
|
+
try {
|
|
166
|
+
const buttonFound = await page.evaluate(() => {
|
|
167
|
+
const buttons = Array.from(document.querySelectorAll('button'));
|
|
168
|
+
for (const btn of buttons) {
|
|
169
|
+
const ariaLabel = btn.getAttribute('aria-label') || '';
|
|
170
|
+
const textContent = btn.textContent || '';
|
|
171
|
+
const isVisible = btn.offsetParent !== null &&
|
|
172
|
+
window.getComputedStyle(btn).visibility !== 'hidden' &&
|
|
173
|
+
window.getComputedStyle(btn).display !== 'none';
|
|
174
|
+
|
|
175
|
+
if (isVisible && (ariaLabel.startsWith('Message') ||
|
|
176
|
+
(textContent.includes('Message') && !textContent.includes('Pending')))) {
|
|
177
|
+
const rect = btn.getBoundingClientRect();
|
|
178
|
+
if (rect.width > 0 && rect.height > 0) {
|
|
179
|
+
return {
|
|
180
|
+
found: true,
|
|
181
|
+
x: rect.left + rect.width / 2,
|
|
182
|
+
y: rect.top + rect.height / 2
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return { found: false };
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (buttonFound.found) {
|
|
191
|
+
await page.mouse.click(buttonFound.x, buttonFound.y);
|
|
192
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
193
|
+
return await checkOverlayAppeared(page);
|
|
194
|
+
}
|
|
195
|
+
} catch (error) {
|
|
196
|
+
}
|
|
197
|
+
return false;
|
|
198
|
+
},
|
|
199
|
+
async () => {
|
|
200
|
+
try {
|
|
201
|
+
const messageBtn = await getButtonByText(page, 'Message', true);
|
|
202
|
+
if (messageBtn) {
|
|
203
|
+
await messageBtn.scrollIntoViewIfNeeded();
|
|
204
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
205
|
+
await messageBtn.click({ timeout: 5000, force: false });
|
|
206
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
207
|
+
return await checkOverlayAppeared(page);
|
|
208
|
+
}
|
|
209
|
+
} catch (error) {
|
|
210
|
+
}
|
|
211
|
+
return false;
|
|
212
|
+
},
|
|
213
|
+
async () => {
|
|
214
|
+
try {
|
|
215
|
+
const messageBtn = await getButtonByText(page, 'Message', false);
|
|
216
|
+
if (messageBtn) {
|
|
217
|
+
await messageBtn.scrollIntoViewIfNeeded();
|
|
218
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
219
|
+
await messageBtn.click({ timeout: 5000, force: false });
|
|
220
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
221
|
+
return await checkOverlayAppeared(page);
|
|
222
|
+
}
|
|
223
|
+
} catch (error) {
|
|
224
|
+
}
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
];
|
|
38
228
|
|
|
229
|
+
for (const strategy of clickStrategies) {
|
|
230
|
+
try {
|
|
231
|
+
clicked = await strategy();
|
|
232
|
+
if (clicked) {
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
} catch (error) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
39
240
|
if (!clicked) {
|
|
40
241
|
return {
|
|
41
242
|
action: 'get_messages',
|
|
@@ -44,18 +245,75 @@ export async function executeGetMessages(page, action) {
|
|
|
44
245
|
};
|
|
45
246
|
}
|
|
46
247
|
|
|
47
|
-
|
|
48
|
-
|
|
248
|
+
try {
|
|
249
|
+
await page.waitForSelector('.msg-overlay-conversation-bubble__content-wrapper', { timeout: 15000 });
|
|
250
|
+
} catch (error) {
|
|
251
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
252
|
+
const overlayExists = await page.locator('.msg-overlay-conversation-bubble__content-wrapper').count();
|
|
253
|
+
if (overlayExists === 0) {
|
|
254
|
+
return {
|
|
255
|
+
action: 'get_messages',
|
|
256
|
+
result: { status: 'error', message: `Clicked Message button but overlay did not appear for ${username}` },
|
|
257
|
+
status: 'failed'
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 2000) + 1000));
|
|
49
263
|
|
|
50
264
|
await page.evaluate(() => {
|
|
51
|
-
const
|
|
52
|
-
if (
|
|
53
|
-
|
|
265
|
+
const messageList = document.querySelector('.msg-s-message-list');
|
|
266
|
+
if (messageList) {
|
|
267
|
+
messageList.scrollTop = 0;
|
|
54
268
|
}
|
|
55
269
|
});
|
|
56
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
270
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
271
|
+
|
|
272
|
+
let previousScrollHeight = 0;
|
|
273
|
+
let scrollAttempts = 0;
|
|
274
|
+
const maxScrollAttempts = 10;
|
|
275
|
+
|
|
276
|
+
while (scrollAttempts < maxScrollAttempts) {
|
|
277
|
+
const scrollResult = await page.evaluate(() => {
|
|
278
|
+
const messageList = document.querySelector('.msg-s-message-list');
|
|
279
|
+
if (!messageList) return { shouldContinue: false, scrollHeight: 0 };
|
|
280
|
+
|
|
281
|
+
const currentScrollHeight = messageList.scrollHeight;
|
|
282
|
+
messageList.scrollTop = messageList.scrollHeight;
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
shouldContinue: true,
|
|
286
|
+
scrollHeight: currentScrollHeight,
|
|
287
|
+
scrollTop: messageList.scrollTop
|
|
288
|
+
};
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
if (!scrollResult.shouldContinue) {
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
296
|
+
|
|
297
|
+
const newScrollHeight = await page.evaluate(() => {
|
|
298
|
+
const messageList = document.querySelector('.msg-s-message-list');
|
|
299
|
+
return messageList ? messageList.scrollHeight : 0;
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
if (newScrollHeight === previousScrollHeight) {
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
previousScrollHeight = newScrollHeight;
|
|
307
|
+
scrollAttempts++;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
57
311
|
|
|
58
312
|
const content = await page.evaluate(() => {
|
|
313
|
+
const messageList = document.querySelector('.msg-s-message-list');
|
|
314
|
+
if (messageList) {
|
|
315
|
+
return messageList.innerText || '';
|
|
316
|
+
}
|
|
59
317
|
const wrapper = document.querySelector('.msg-overlay-conversation-bubble__content-wrapper');
|
|
60
318
|
return wrapper ? wrapper.innerText : '';
|
|
61
319
|
});
|
|
@@ -68,24 +326,32 @@ export async function executeGetMessages(page, action) {
|
|
|
68
326
|
}
|
|
69
327
|
|
|
70
328
|
async function getButtonByText(page, text, inMain = false) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
329
|
+
try {
|
|
330
|
+
let buttons = [];
|
|
331
|
+
if (inMain) {
|
|
332
|
+
buttons = await page.locator('main button').all();
|
|
333
|
+
} else {
|
|
334
|
+
buttons = await page.locator('button').all();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
for (const button of buttons) {
|
|
338
|
+
try {
|
|
339
|
+
const buttonText = await button.textContent().catch(() => '');
|
|
340
|
+
const ariaLabel = await button.getAttribute('aria-label').catch(() => '');
|
|
341
|
+
const isVisible = await button.isVisible().catch(() => false);
|
|
342
|
+
|
|
343
|
+
if (isVisible && ((buttonText && buttonText.trim().includes(text)) ||
|
|
344
|
+
(ariaLabel && ariaLabel.includes(text)))) {
|
|
345
|
+
const boundingBox = await button.boundingBox().catch(() => null);
|
|
346
|
+
if (boundingBox && boundingBox.width > 0 && boundingBox.height > 0) {
|
|
347
|
+
return button;
|
|
348
|
+
}
|
|
85
349
|
}
|
|
350
|
+
} catch (error) {
|
|
351
|
+
continue;
|
|
86
352
|
}
|
|
87
|
-
} catch (error) {
|
|
88
353
|
}
|
|
354
|
+
} catch (error) {
|
|
89
355
|
}
|
|
90
356
|
|
|
91
357
|
return null;
|