coffeeinabit 0.0.1
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/cloud_auth.js +170 -0
- package/linkedin_automation.js +784 -0
- package/package.json +28 -0
- package/public/dashboard.html +533 -0
- package/public/login.html +163 -0
- package/public/styles.css +298 -0
- package/server.js +201 -0
- package/tools/check_connection_status.js +32 -0
- package/tools/comment_post.js +88 -0
- package/tools/cookie_manager.js +148 -0
- package/tools/get_daily_linkedin_connections.js +86 -0
- package/tools/get_linkedin_search_results.js +141 -0
- package/tools/get_linkedin_updates.js +51 -0
- package/tools/get_messages.js +92 -0
- package/tools/get_new_messages.js +423 -0
- package/tools/get_profile.js +255 -0
- package/tools/human_typing.js +32 -0
- package/tools/like_post.js +57 -0
- package/tools/send_connection_request.js +155 -0
- package/tools/send_messages.js +162 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export async function executeCommentPost(page, action) {
|
|
2
|
+
const postUrl = action.parameters?.url || action.url;
|
|
3
|
+
const commentText = action.parameters?.comment_text || action.comment_text;
|
|
4
|
+
|
|
5
|
+
if (!postUrl || !commentText) {
|
|
6
|
+
throw new Error('Missing url or comment_text for comment_post');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
await page.goto(postUrl, {
|
|
10
|
+
waitUntil: 'domcontentloaded',
|
|
11
|
+
timeout: Math.floor(Math.random() * 20000) + 40000
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
await page.waitForSelector('main', { timeout: 10000 });
|
|
15
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 10000) + 500));
|
|
16
|
+
await page.evaluate(() => { window.scrollTo(0, document.body.scrollHeight / 2); });
|
|
17
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 4000) + 500));
|
|
18
|
+
|
|
19
|
+
let commentEditor = await page.locator('.comments-comment-box-comment__text-editor .ql-editor[contenteditable="true"]').first();
|
|
20
|
+
|
|
21
|
+
if (await commentEditor.count() === 0) {
|
|
22
|
+
const altEditor = await page.locator('[data-test-ql-editor-contenteditable="true"]').first();
|
|
23
|
+
|
|
24
|
+
if (await altEditor.count() === 0) {
|
|
25
|
+
return {
|
|
26
|
+
action: 'comment_post',
|
|
27
|
+
result: { status: 'not_found', message: `Could not find comment editor for post: ${postUrl}` },
|
|
28
|
+
status: 'failed'
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
await altEditor.click();
|
|
33
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 1000) + 500));
|
|
34
|
+
await altEditor.fill(commentText);
|
|
35
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 1000) + 500));
|
|
36
|
+
|
|
37
|
+
let submitButton = await page.locator('.comments-comment-box__submit-button--cr').first();
|
|
38
|
+
|
|
39
|
+
if (await submitButton.count() > 0) {
|
|
40
|
+
await submitButton.click();
|
|
41
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 2000) + 500));
|
|
42
|
+
} else {
|
|
43
|
+
const altSubmitButton = await page.locator('button:has-text("Comment")').first();
|
|
44
|
+
|
|
45
|
+
if (await altSubmitButton.count() > 0) {
|
|
46
|
+
await altSubmitButton.click();
|
|
47
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 2000) + 500));
|
|
48
|
+
} else {
|
|
49
|
+
return {
|
|
50
|
+
action: 'comment_post',
|
|
51
|
+
result: { status: 'submit_not_found', message: `Could not find Comment submit button for post: ${postUrl}` },
|
|
52
|
+
status: 'failed'
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
await commentEditor.click();
|
|
58
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 1000) + 500));
|
|
59
|
+
await commentEditor.fill(commentText);
|
|
60
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 1000) + 500));
|
|
61
|
+
|
|
62
|
+
let submitButton = await page.locator('.comments-comment-box__submit-button--cr').first();
|
|
63
|
+
|
|
64
|
+
if (await submitButton.count() > 0) {
|
|
65
|
+
await submitButton.click();
|
|
66
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 2000) + 500));
|
|
67
|
+
} else {
|
|
68
|
+
const altSubmitButton = await page.locator('button:has-text("Comment")').first();
|
|
69
|
+
|
|
70
|
+
if (await altSubmitButton.count() > 0) {
|
|
71
|
+
await altSubmitButton.click();
|
|
72
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 2000) + 500));
|
|
73
|
+
} else {
|
|
74
|
+
return {
|
|
75
|
+
action: 'comment_post',
|
|
76
|
+
result: { status: 'submit_not_found', message: `Could not find Comment submit button for post: ${postUrl}` },
|
|
77
|
+
status: 'failed'
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
action: 'comment_post',
|
|
85
|
+
result: { status: 'success', message: `Comment successfully posted on post: ${postUrl}` },
|
|
86
|
+
status: 'success'
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export class CookieManager {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.contextDir = path.join(process.cwd(), 'context');
|
|
7
|
+
this.ensureContextDir();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
ensureContextDir() {
|
|
11
|
+
if (!fs.existsSync(this.contextDir)) {
|
|
12
|
+
fs.mkdirSync(this.contextDir, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
getCookieFilePath(userEmail) {
|
|
17
|
+
const sanitizedEmail = userEmail.replace(/[^a-zA-Z0-9]/g, '_');
|
|
18
|
+
return path.join(this.contextDir, `linkedin_cookies_${sanitizedEmail}.json`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async saveCookies(userEmail, cookies) {
|
|
22
|
+
try {
|
|
23
|
+
const cookieFilePath = this.getCookieFilePath(userEmail);
|
|
24
|
+
const cookieData = {
|
|
25
|
+
userEmail: userEmail,
|
|
26
|
+
cookies: cookies,
|
|
27
|
+
savedAt: new Date().toISOString(),
|
|
28
|
+
expiresAt: this.calculateExpiryTime()
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
fs.writeFileSync(cookieFilePath, JSON.stringify(cookieData, null, 2));
|
|
32
|
+
console.log('[CookieManager] Cookies saved for:', userEmail);
|
|
33
|
+
return true;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error('[CookieManager] Error saving cookies:', error.message);
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async loadCookies(userEmail) {
|
|
41
|
+
try {
|
|
42
|
+
const cookieFilePath = this.getCookieFilePath(userEmail);
|
|
43
|
+
|
|
44
|
+
if (!fs.existsSync(cookieFilePath)) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const cookieData = JSON.parse(fs.readFileSync(cookieFilePath, 'utf8'));
|
|
49
|
+
|
|
50
|
+
if (this.isCookieDataExpired(cookieData)) {
|
|
51
|
+
this.deleteCookies(userEmail);
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log('[CookieManager] Loaded cookies for:', userEmail);
|
|
56
|
+
return cookieData.cookies;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('[CookieManager] Error loading cookies:', error.message);
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async deleteCookies(userEmail) {
|
|
64
|
+
try {
|
|
65
|
+
const cookieFilePath = this.getCookieFilePath(userEmail);
|
|
66
|
+
if (fs.existsSync(cookieFilePath)) {
|
|
67
|
+
fs.unlinkSync(cookieFilePath);
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error('[CookieManager] Error deleting cookies:', error.message);
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async setCookiesInContext(context, cookies) {
|
|
77
|
+
try {
|
|
78
|
+
if (!cookies || cookies.length === 0) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
await context.addCookies(cookies);
|
|
83
|
+
return true;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error('[CookieManager] Error setting cookies in context:', error.message);
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async verifyAuthentication(context) {
|
|
91
|
+
try {
|
|
92
|
+
const page = await context.newPage();
|
|
93
|
+
|
|
94
|
+
await page.goto('https://www.linkedin.com/feed/', {
|
|
95
|
+
waitUntil: 'domcontentloaded',
|
|
96
|
+
timeout: 60000
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const currentUrl = page.url();
|
|
100
|
+
const isAuthenticated = currentUrl.includes('/feed') && !currentUrl.includes('/login');
|
|
101
|
+
|
|
102
|
+
return { isAuthenticated, page };
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error('[CookieManager] Error verifying authentication:', error.message);
|
|
105
|
+
return { isAuthenticated: false, page: null };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
calculateExpiryTime() {
|
|
110
|
+
const now = new Date();
|
|
111
|
+
const expiryTime = new Date(now.getTime() + (7 * 24 * 60 * 60 * 1000));
|
|
112
|
+
return expiryTime.toISOString();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
isCookieDataExpired(cookieData) {
|
|
116
|
+
if (!cookieData.expiresAt) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const now = new Date();
|
|
121
|
+
const expiryTime = new Date(cookieData.expiresAt);
|
|
122
|
+
return now > expiryTime;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async getCookieInfo(userEmail) {
|
|
126
|
+
try {
|
|
127
|
+
const cookieFilePath = this.getCookieFilePath(userEmail);
|
|
128
|
+
|
|
129
|
+
if (!fs.existsSync(cookieFilePath)) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const cookieData = JSON.parse(fs.readFileSync(cookieFilePath, 'utf8'));
|
|
134
|
+
return {
|
|
135
|
+
userEmail: cookieData.userEmail,
|
|
136
|
+
savedAt: cookieData.savedAt,
|
|
137
|
+
expiresAt: cookieData.expiresAt,
|
|
138
|
+
isExpired: this.isCookieDataExpired(cookieData),
|
|
139
|
+
cookieCount: cookieData.cookies ? cookieData.cookies.length : 0
|
|
140
|
+
};
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.error('[CookieManager] Error getting cookie info:', error.message);
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export default CookieManager;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export async function executeGetDailyLinkedInConnections(page, action) {
|
|
2
|
+
const lastCheckTimestamp = action.parameters?.last_check_timestamp
|
|
3
|
+
? parseInt(action.parameters.last_check_timestamp, 10)
|
|
4
|
+
: null;
|
|
5
|
+
|
|
6
|
+
const url = 'https://www.linkedin.com/mynetwork/invite-connect/connections/';
|
|
7
|
+
await page.goto(url, {
|
|
8
|
+
waitUntil: 'domcontentloaded',
|
|
9
|
+
timeout: 60000
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
await page.waitForSelector('a[href*="/in/"]', {
|
|
13
|
+
timeout: Math.floor(Math.random() * 5000) + 5000
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 5000) + 500));
|
|
17
|
+
|
|
18
|
+
const totalConnections = await page.evaluate(() => {
|
|
19
|
+
const headerElement = document.querySelector('h1');
|
|
20
|
+
if (headerElement) {
|
|
21
|
+
const match = headerElement.textContent.match(/(\d+)\s+Connections?/i);
|
|
22
|
+
return match ? parseInt(match[1]) : 0;
|
|
23
|
+
}
|
|
24
|
+
return 0;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const now = Date.now();
|
|
28
|
+
const newConnections = await page.evaluate(({ lastCheckTimestamp, now }) => {
|
|
29
|
+
function parseConnectionDate(str) {
|
|
30
|
+
if (!str) return null;
|
|
31
|
+
const match = str.match(/Connected on (.+)/i);
|
|
32
|
+
if (!match) return null;
|
|
33
|
+
const dateStr = match[1].trim();
|
|
34
|
+
try {
|
|
35
|
+
const timestamp = new Date(dateStr).getTime();
|
|
36
|
+
return isNaN(timestamp) ? null : timestamp;
|
|
37
|
+
} catch (e) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const connections = [];
|
|
43
|
+
const allParagraphs = document.querySelectorAll('p');
|
|
44
|
+
|
|
45
|
+
allParagraphs.forEach(p => {
|
|
46
|
+
const text = p.textContent.trim();
|
|
47
|
+
if (text.startsWith('Connected on')) {
|
|
48
|
+
const parentDiv = p.parentElement;
|
|
49
|
+
if (parentDiv) {
|
|
50
|
+
const profileLink = parentDiv.querySelector('a[href*="/in/"]');
|
|
51
|
+
if (profileLink) {
|
|
52
|
+
const href = profileLink.getAttribute('href');
|
|
53
|
+
const usernameMatch = href.match(/\/in\/([^/?]+)/);
|
|
54
|
+
if (usernameMatch) {
|
|
55
|
+
const username = usernameMatch[1];
|
|
56
|
+
const connectionDate = text;
|
|
57
|
+
const connectionTimestamp = parseConnectionDate(connectionDate);
|
|
58
|
+
|
|
59
|
+
if (!lastCheckTimestamp || (connectionTimestamp && connectionTimestamp >= lastCheckTimestamp)) {
|
|
60
|
+
connections.push({
|
|
61
|
+
username: username,
|
|
62
|
+
connectionDate: connectionDate,
|
|
63
|
+
connectionTimestamp: connectionTimestamp
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return connections;
|
|
73
|
+
}, { lastCheckTimestamp, now });
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
action: 'get_daily_linkedin_connections',
|
|
77
|
+
result: {
|
|
78
|
+
totalConnections: totalConnections,
|
|
79
|
+
newConnections: newConnections,
|
|
80
|
+
newConnectionsCount: newConnections.length,
|
|
81
|
+
lastCheckTimestamp: lastCheckTimestamp,
|
|
82
|
+
currentTimestamp: now
|
|
83
|
+
},
|
|
84
|
+
status: 'success'
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
export async function executeLinkedInSearch(page, action) {
|
|
2
|
+
const linkedinUrl = action.parameters?.url || action.url;
|
|
3
|
+
const maxPages = action.parameters?.max_pages || 10;
|
|
4
|
+
|
|
5
|
+
if (!linkedinUrl) {
|
|
6
|
+
throw new Error('Missing url for get_linkedin_search_results');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let navigationSuccess = true;
|
|
10
|
+
try {
|
|
11
|
+
await page.goto(linkedinUrl, {
|
|
12
|
+
waitUntil: 'load',
|
|
13
|
+
timeout: Math.floor(Math.random() * 20000) + 10000
|
|
14
|
+
});
|
|
15
|
+
} catch (error) {
|
|
16
|
+
navigationSuccess = false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let searchResults = [];
|
|
20
|
+
let currentPage = 1;
|
|
21
|
+
let isLastPage = false;
|
|
22
|
+
let title = '';
|
|
23
|
+
|
|
24
|
+
while (!isLastPage && currentPage <= Math.min(maxPages, 10)) {
|
|
25
|
+
if (!navigationSuccess) {
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
await page.waitForLoadState('domcontentloaded', { timeout: 10000 });
|
|
31
|
+
} catch (error) {
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 10000) + 500));
|
|
35
|
+
|
|
36
|
+
let htmlContent = await page.content();
|
|
37
|
+
let pageResults;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
title = await page.title();
|
|
41
|
+
const listItems = await page.locator('ul[role="list"] li').all();
|
|
42
|
+
|
|
43
|
+
if (listItems.length > 0) {
|
|
44
|
+
const firstItem = listItems[0];
|
|
45
|
+
const firstItemHTML = await firstItem.innerHTML().catch(() => '');
|
|
46
|
+
console.log('[LinkedInSearch] First item HTML structure:', firstItemHTML.substring(0, 500));
|
|
47
|
+
} else {
|
|
48
|
+
console.log('[LinkedInSearch] No list items found');
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const pageSearchResults = await parseLinkedInSearchResultsFromHTML(htmlContent);
|
|
53
|
+
|
|
54
|
+
pageResults = pageSearchResults;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
pageResults = [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
searchResults.push(...pageResults);
|
|
60
|
+
|
|
61
|
+
currentPage++;
|
|
62
|
+
let nextUrl = linkedinUrl.includes('?')
|
|
63
|
+
? linkedinUrl + '&page=' + currentPage
|
|
64
|
+
: linkedinUrl + '?page=' + currentPage;
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
await page.goto(nextUrl, {
|
|
68
|
+
waitUntil: 'load',
|
|
69
|
+
timeout: Math.floor(Math.random() * 20000) + 10000
|
|
70
|
+
});
|
|
71
|
+
navigationSuccess = true;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
navigationSuccess = false;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
action: 'get_linkedin_search_results',
|
|
80
|
+
result: {
|
|
81
|
+
title: title,
|
|
82
|
+
results: searchResults,
|
|
83
|
+
url: linkedinUrl,
|
|
84
|
+
searchResultsCount: searchResults.length,
|
|
85
|
+
pagesProcessed: currentPage,
|
|
86
|
+
last_page: isLastPage
|
|
87
|
+
},
|
|
88
|
+
status: 'success'
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function parseLinkedInSearchResultsFromHTML(htmlContent) {
|
|
93
|
+
try {
|
|
94
|
+
const results = [];
|
|
95
|
+
|
|
96
|
+
const listItemPattern = /<li[^>]*>([\s\S]*?)<\/li>/gi;
|
|
97
|
+
const linkPattern = /<a[^>]*href="([^"]*\/in\/[^"]*)"[^>]*>/gi;
|
|
98
|
+
|
|
99
|
+
let listItemMatch;
|
|
100
|
+
|
|
101
|
+
while ((listItemMatch = listItemPattern.exec(htmlContent)) !== null) {
|
|
102
|
+
const listItemHtml = listItemMatch[1];
|
|
103
|
+
|
|
104
|
+
const linkMatches = [];
|
|
105
|
+
let linkMatch;
|
|
106
|
+
|
|
107
|
+
while ((linkMatch = linkPattern.exec(listItemHtml)) !== null) {
|
|
108
|
+
linkMatches.push(linkMatch[1]);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (linkMatches.length > 0) {
|
|
112
|
+
const href = linkMatches[0];
|
|
113
|
+
let username = '';
|
|
114
|
+
|
|
115
|
+
if (href) {
|
|
116
|
+
const usernameMatch = href.match(/\/in\/([^/?]+)/);
|
|
117
|
+
if (usernameMatch) {
|
|
118
|
+
username = usernameMatch[1];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const textContent = listItemHtml
|
|
123
|
+
.replace(/<[^>]*>/g, ' ')
|
|
124
|
+
.replace(/\s+/g, ' ')
|
|
125
|
+
.trim();
|
|
126
|
+
|
|
127
|
+
results.push({
|
|
128
|
+
link: href,
|
|
129
|
+
username: username,
|
|
130
|
+
item: textContent
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return results;
|
|
136
|
+
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error('[LinkedInSearch] Error parsing search results:', error);
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { executeGetDailyLinkedInConnections } from './get_daily_linkedin_connections.js';
|
|
2
|
+
import { executeGetNewMessages } from './get_new_messages.js';
|
|
3
|
+
|
|
4
|
+
export async function executeGetLinkedInUpdates(page, action, accessToken) {
|
|
5
|
+
try {
|
|
6
|
+
console.log('[GetLinkedInUpdates] Starting unified updates check...');
|
|
7
|
+
|
|
8
|
+
const lastChecked = action.parameters?.last_checked || 0;
|
|
9
|
+
|
|
10
|
+
console.log('[GetLinkedInUpdates] Fetching new messages...');
|
|
11
|
+
const messagesResult = await executeGetNewMessages(page, action, accessToken);
|
|
12
|
+
|
|
13
|
+
console.log('[GetLinkedInUpdates] Waiting before fetching connections...');
|
|
14
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
15
|
+
|
|
16
|
+
console.log('[GetLinkedInUpdates] Fetching new connections...');
|
|
17
|
+
const connectionsAction = {
|
|
18
|
+
action: 'get_daily_linkedin_connections',
|
|
19
|
+
parameters: {
|
|
20
|
+
last_check_timestamp: lastChecked
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const connectionsResult = await executeGetDailyLinkedInConnections(page, connectionsAction);
|
|
24
|
+
|
|
25
|
+
console.log('[GetLinkedInUpdates] Connections result:', JSON.stringify(connectionsResult, null, 2));
|
|
26
|
+
console.log('[GetLinkedInUpdates] Messages result:', JSON.stringify(messagesResult, null, 2));
|
|
27
|
+
|
|
28
|
+
const results = {
|
|
29
|
+
newConnections: connectionsResult.result?.newConnections || [],
|
|
30
|
+
newConnectionsCount: connectionsResult.result?.newConnectionsCount || 0,
|
|
31
|
+
totalConnections: connectionsResult.result?.totalConnections || 0,
|
|
32
|
+
newMessages: messagesResult.messages || []
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
console.log('[GetLinkedInUpdates] Unified results:', {
|
|
36
|
+
newConnectionsCount: results.newConnectionsCount,
|
|
37
|
+
newConnectionsArray: results.newConnections.length,
|
|
38
|
+
totalConnections: results.totalConnections,
|
|
39
|
+
newMessagesCount: results.newMessages.length
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
action: 'get_linkedin_updates',
|
|
44
|
+
result: results,
|
|
45
|
+
status: 'success'
|
|
46
|
+
};
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error('[GetLinkedInUpdates] Error:', error.message);
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
export async function executeGetMessages(page, action) {
|
|
2
|
+
const username = action.parameters?.username || action.username;
|
|
3
|
+
|
|
4
|
+
if (!username) {
|
|
5
|
+
throw new Error('Missing username for get_messages');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const profileUrl = `https://www.linkedin.com/in/${username}`;
|
|
9
|
+
await page.goto(profileUrl, {
|
|
10
|
+
waitUntil: 'load',
|
|
11
|
+
timeout: Math.floor(Math.random() * 20000) + 40000
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
await page.waitForLoadState('domcontentloaded');
|
|
15
|
+
|
|
16
|
+
const pendingButton = await getButtonByText(page, 'Pending', true);
|
|
17
|
+
if (pendingButton) {
|
|
18
|
+
return {
|
|
19
|
+
action: 'get_messages',
|
|
20
|
+
result: { status: 'connection_request_pending', message: `Connection request pending for ${username}. Not connected` },
|
|
21
|
+
status: 'pending'
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const clicked = await page.evaluate(() => {
|
|
26
|
+
const buttons = Array.from(document.querySelectorAll('.artdeco-button--primary'));
|
|
27
|
+
console.log('found buttons: ', buttons);
|
|
28
|
+
const messageBtn = buttons.find(btn =>
|
|
29
|
+
btn.textContent.includes('Message') && btn.offsetParent !== null
|
|
30
|
+
);
|
|
31
|
+
if (messageBtn) {
|
|
32
|
+
messageBtn.click();
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
console.log('no messageBtn found');
|
|
36
|
+
return false;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (!clicked) {
|
|
40
|
+
return {
|
|
41
|
+
action: 'get_messages',
|
|
42
|
+
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.` },
|
|
43
|
+
status: 'failed'
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
await page.waitForSelector('.msg-overlay-conversation-bubble__content-wrapper', { timeout: 10000 });
|
|
48
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 5000) + 500));
|
|
49
|
+
|
|
50
|
+
await page.evaluate(() => {
|
|
51
|
+
const wrapper = document.querySelector('.msg-overlay-conversation-bubble__content-wrapper');
|
|
52
|
+
if (wrapper) {
|
|
53
|
+
wrapper.scrollTo(0, 0);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 5000) + 500));
|
|
57
|
+
|
|
58
|
+
const content = await page.evaluate(() => {
|
|
59
|
+
const wrapper = document.querySelector('.msg-overlay-conversation-bubble__content-wrapper');
|
|
60
|
+
return wrapper ? wrapper.innerText : '';
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
action: 'get_messages',
|
|
65
|
+
result: { status: 'success', message: content },
|
|
66
|
+
status: 'success'
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function getButtonByText(page, text, inMain = false) {
|
|
71
|
+
let buttons = [];
|
|
72
|
+
if (inMain) {
|
|
73
|
+
buttons = await page.locator('main button').all();
|
|
74
|
+
} else {
|
|
75
|
+
buttons = await page.locator('button').all();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const button of buttons) {
|
|
79
|
+
try {
|
|
80
|
+
const buttonText = await button.textContent();
|
|
81
|
+
if (buttonText && buttonText.includes(text)) {
|
|
82
|
+
const isVisible = await button.isVisible();
|
|
83
|
+
if (isVisible) {
|
|
84
|
+
return button;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} catch (error) {
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return null;
|
|
92
|
+
}
|