coffeeinabit 0.0.47 → 0.0.49
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 +226 -11
- package/package.json +2 -1
- package/server.js +50 -3
- package/state/app_state.js +62 -3
- package/tools/get_daily_linkedin_connections.js +144 -83
- package/tools/get_new_messages.js +468 -323
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import { safeGoto } from './navigation.js';
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Parse lastChecked timestamp from action parameters
|
|
6
|
+
*/
|
|
7
|
+
function parseLastCheckedTimestamp(action) {
|
|
5
8
|
let lastChecked = action.parameters?.last_checked;
|
|
6
9
|
if (!lastChecked) {
|
|
7
10
|
lastChecked = 0;
|
|
@@ -12,14 +15,112 @@ export async function executeGetNewMessages(page, action, accessToken) {
|
|
|
12
15
|
lastCheckedTimestamp = lastCheckedTimestamp * 1000;
|
|
13
16
|
}
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
return lastCheckedTimestamp;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Extract participant encoded URL from conversation data
|
|
23
|
+
*/
|
|
24
|
+
function extractParticipantUrl(conversation) {
|
|
25
|
+
const convUrn = conversation.backendUrn;
|
|
26
|
+
const participants = conversation.conversationParticipants || [];
|
|
27
|
+
const otherPerson = participants.find(p => {
|
|
28
|
+
const member = p.participantType?.member;
|
|
29
|
+
return member && member.distance !== 'SELF';
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (otherPerson && otherPerson.hostIdentityUrn) {
|
|
33
|
+
const encodedUrl = otherPerson.hostIdentityUrn.includes('urn:li:fsd_profile:')
|
|
34
|
+
? otherPerson.hostIdentityUrn.split('urn:li:fsd_profile:')[1]
|
|
35
|
+
: '';
|
|
36
|
+
return { convUrn, encodedUrl };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { convUrn, encodedUrl: null };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Parse conversation participants from API response
|
|
44
|
+
*/
|
|
45
|
+
function parseConversationParticipants(data, conversationParticipantsMap) {
|
|
46
|
+
if (data?.data?.messengerConversationsBySyncToken?.elements) {
|
|
47
|
+
const conversations = data.data.messengerConversationsBySyncToken.elements;
|
|
48
|
+
conversations.forEach(conv => {
|
|
49
|
+
const { convUrn, encodedUrl } = extractParticipantUrl(conv);
|
|
50
|
+
if (encodedUrl) {
|
|
51
|
+
conversationParticipantsMap.set(convUrn, encodedUrl);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Extract message entity from API response message object
|
|
59
|
+
*/
|
|
60
|
+
function extractMessageEntity(msg) {
|
|
61
|
+
const senderInfo = msg.sender || msg.actor;
|
|
62
|
+
const senderMember = senderInfo?.participantType?.member;
|
|
63
|
+
const authorId = senderInfo?.backendUrn || 'unknown';
|
|
64
|
+
const authorName = senderMember
|
|
65
|
+
? `${senderMember.firstName?.text || ''} ${senderMember.lastName?.text || ''}`.trim()
|
|
66
|
+
: 'Unknown';
|
|
67
|
+
|
|
68
|
+
const hostIdentityUrn = senderInfo?.hostIdentityUrn || '';
|
|
69
|
+
const authorUrl = hostIdentityUrn.includes('urn:li:fsd_profile:')
|
|
70
|
+
? hostIdentityUrn.split('urn:li:fsd_profile:')[1]
|
|
71
|
+
: '';
|
|
72
|
+
|
|
73
|
+
const conversationId = msg.backendConversationUrn || '';
|
|
74
|
+
const isSelf = senderMember?.distance === 'SELF';
|
|
75
|
+
const messageText = msg.body?.text || '';
|
|
76
|
+
const deliveredAt = msg.deliveredAt;
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
authorId,
|
|
80
|
+
authorName,
|
|
81
|
+
authorUrl,
|
|
82
|
+
conversationId,
|
|
83
|
+
isSelf,
|
|
84
|
+
messageText,
|
|
85
|
+
deliveredAt
|
|
86
|
+
};
|
|
87
|
+
}
|
|
18
88
|
|
|
19
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Parse messages from API response
|
|
91
|
+
*/
|
|
92
|
+
function parseMessagesFromResponse(data, allMessageEntities, processedMessages) {
|
|
93
|
+
if (data?.data?.messengerMessagesBySyncToken?.elements) {
|
|
94
|
+
const elements = data.data.messengerMessagesBySyncToken.elements;
|
|
95
|
+
console.log(`[GetNewMessages] Found messengerMessagesBySyncToken with ${elements.length} messages`);
|
|
96
|
+
|
|
97
|
+
let addedCount = 0;
|
|
98
|
+
let duplicateCount = 0;
|
|
99
|
+
|
|
100
|
+
elements.forEach(msg => {
|
|
101
|
+
const messageEntity = extractMessageEntity(msg);
|
|
102
|
+
const messageKey = `${messageEntity.conversationId}-${messageEntity.deliveredAt}-${messageEntity.messageText}`;
|
|
103
|
+
|
|
104
|
+
if (messageEntity.messageText && messageEntity.deliveredAt && !processedMessages.has(messageKey)) {
|
|
105
|
+
processedMessages.add(messageKey);
|
|
106
|
+
allMessageEntities.push(messageEntity);
|
|
107
|
+
addedCount++;
|
|
108
|
+
} else if (processedMessages.has(messageKey)) {
|
|
109
|
+
duplicateCount++;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
console.log(`[GetNewMessages] Added ${addedCount} new messages, ${duplicateCount} duplicates, total now: ${allMessageEntities.length}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Create route handler for intercepting LinkedIn API calls
|
|
119
|
+
*/
|
|
120
|
+
function createRouteHandler(allMessageEntities, processedMessages, conversationParticipantsMap) {
|
|
121
|
+
return async (route) => {
|
|
20
122
|
const url = route.request().url();
|
|
21
123
|
|
|
22
|
-
/////
|
|
23
124
|
if (url.includes('voyagerMessagingGraphQL')) {
|
|
24
125
|
const requestBody = route.request().postData();
|
|
25
126
|
if (requestBody) {
|
|
@@ -37,27 +138,7 @@ export async function executeGetNewMessages(page, action, accessToken) {
|
|
|
37
138
|
try {
|
|
38
139
|
const responseBody = await response.text();
|
|
39
140
|
const data = JSON.parse(responseBody);
|
|
40
|
-
|
|
41
|
-
if (data?.data?.messengerConversationsBySyncToken?.elements) {
|
|
42
|
-
const conversations = data.data.messengerConversationsBySyncToken.elements;
|
|
43
|
-
conversations.forEach(conv => {
|
|
44
|
-
const convUrn = conv.backendUrn;
|
|
45
|
-
const participants = conv.conversationParticipants || [];
|
|
46
|
-
const otherPerson = participants.find(p => {
|
|
47
|
-
const member = p.participantType?.member;
|
|
48
|
-
return member && member.distance !== 'SELF';
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
if (otherPerson && otherPerson.hostIdentityUrn) {
|
|
52
|
-
const encodedUrl = otherPerson.hostIdentityUrn.includes('urn:li:fsd_profile:')
|
|
53
|
-
? otherPerson.hostIdentityUrn.split('urn:li:fsd_profile:')[1]
|
|
54
|
-
: '';
|
|
55
|
-
if (encodedUrl) {
|
|
56
|
-
conversationParticipantsMap.set(convUrn, encodedUrl);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
}
|
|
141
|
+
parseConversationParticipants(data, conversationParticipantsMap);
|
|
61
142
|
} catch (error) {
|
|
62
143
|
console.error('[GetNewMessages] Error parsing conversations:', error);
|
|
63
144
|
}
|
|
@@ -75,56 +156,8 @@ export async function executeGetNewMessages(page, action, accessToken) {
|
|
|
75
156
|
try {
|
|
76
157
|
const responseBody = await response.text();
|
|
77
158
|
const data = JSON.parse(responseBody);
|
|
78
|
-
|
|
79
159
|
console.log(`[GetNewMessages] Intercepted messengerMessages response`);
|
|
80
|
-
|
|
81
|
-
if (data?.data?.messengerMessagesBySyncToken?.elements) {
|
|
82
|
-
const elements = data.data.messengerMessagesBySyncToken.elements;
|
|
83
|
-
console.log(`[GetNewMessages] Found messengerMessagesBySyncToken with ${elements.length} messages`);
|
|
84
|
-
|
|
85
|
-
let addedCount = 0;
|
|
86
|
-
let duplicateCount = 0;
|
|
87
|
-
|
|
88
|
-
elements.forEach(msg => {
|
|
89
|
-
const senderInfo = msg.sender || msg.actor;
|
|
90
|
-
const senderMember = senderInfo?.participantType?.member;
|
|
91
|
-
const authorId = senderInfo?.backendUrn || 'unknown';
|
|
92
|
-
const authorName = senderMember
|
|
93
|
-
? `${senderMember.firstName?.text || ''} ${senderMember.lastName?.text || ''}`.trim()
|
|
94
|
-
: 'Unknown';
|
|
95
|
-
|
|
96
|
-
const hostIdentityUrn = senderInfo?.hostIdentityUrn || '';
|
|
97
|
-
const authorUrl = hostIdentityUrn.includes('urn:li:fsd_profile:')
|
|
98
|
-
? hostIdentityUrn.split('urn:li:fsd_profile:')[1]
|
|
99
|
-
: '';
|
|
100
|
-
|
|
101
|
-
const conversationId = msg.backendConversationUrn || '';
|
|
102
|
-
const isSelf = senderMember?.distance === 'SELF';
|
|
103
|
-
|
|
104
|
-
const messageText = msg.body?.text || '';
|
|
105
|
-
const deliveredAt = msg.deliveredAt;
|
|
106
|
-
|
|
107
|
-
const messageKey = `${conversationId}-${deliveredAt}-${messageText}`;
|
|
108
|
-
|
|
109
|
-
if (messageText && deliveredAt && !processedMessages.has(messageKey)) {
|
|
110
|
-
processedMessages.add(messageKey);
|
|
111
|
-
allMessageEntities.push({
|
|
112
|
-
authorId: authorId,
|
|
113
|
-
authorName: authorName,
|
|
114
|
-
authorUrl: authorUrl,
|
|
115
|
-
conversationId: conversationId,
|
|
116
|
-
isSelf: isSelf,
|
|
117
|
-
messageText: messageText,
|
|
118
|
-
deliveredAt: deliveredAt
|
|
119
|
-
});
|
|
120
|
-
addedCount++;
|
|
121
|
-
} else if (processedMessages.has(messageKey)) {
|
|
122
|
-
duplicateCount++;
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
console.log(`[GetNewMessages] Added ${addedCount} new messages, ${duplicateCount} duplicates, total now: ${allMessageEntities.length}`);
|
|
127
|
-
}
|
|
160
|
+
parseMessagesFromResponse(data, allMessageEntities, processedMessages);
|
|
128
161
|
} catch (error) {
|
|
129
162
|
console.error('[GetNewMessages] Error parsing response:', error);
|
|
130
163
|
}
|
|
@@ -138,73 +171,382 @@ export async function executeGetNewMessages(page, action, accessToken) {
|
|
|
138
171
|
|
|
139
172
|
await route.continue();
|
|
140
173
|
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Navigate to messaging page and wait for UI to load
|
|
178
|
+
*/
|
|
179
|
+
async function navigateToMessagingPage(page) {
|
|
180
|
+
await safeGoto(page, 'https://www.linkedin.com/messaging/', {
|
|
181
|
+
waitUntil: 'domcontentloaded',
|
|
182
|
+
timeout: 60000
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
await page.waitForLoadState('domcontentloaded');
|
|
186
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
187
|
+
}
|
|
141
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Wait for conversations list to appear
|
|
191
|
+
* Returns early with empty result if not found (STOPPING POINT #1)
|
|
192
|
+
*/
|
|
193
|
+
async function waitForConversationsList(page, routeHandler) {
|
|
194
|
+
console.log('[GetNewMessages] Waiting for conversations list to load...');
|
|
195
|
+
|
|
142
196
|
try {
|
|
143
|
-
await page.
|
|
197
|
+
await page.waitForSelector('.msg-conversations-container__conversations-list', {
|
|
198
|
+
timeout: 30000,
|
|
199
|
+
state: 'visible'
|
|
200
|
+
});
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.log('[GetNewMessages] Timeout waiting for conversations list');
|
|
203
|
+
try {
|
|
204
|
+
await page.unroute('**/voyagerMessagingGraphQL/**', routeHandler);
|
|
205
|
+
} catch (unrouteError) {
|
|
206
|
+
console.log('[GetNewMessages] Error unrouting (non-critical):', unrouteError.message);
|
|
207
|
+
}
|
|
208
|
+
// ⚠️ STOPPING POINT #1: Early return when UI not available
|
|
209
|
+
return { success: false, items: null };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
213
|
+
return { success: true, items: null };
|
|
214
|
+
}
|
|
144
215
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
216
|
+
/**
|
|
217
|
+
* Get conversation items from the list
|
|
218
|
+
* Returns early with empty result if none found (STOPPING POINT #2 & #3)
|
|
219
|
+
*/
|
|
220
|
+
async function getConversationItems(page, routeHandler) {
|
|
221
|
+
console.log('[GetNewMessages] Looking for conversations list...');
|
|
222
|
+
const conversationsList = await page.locator('.msg-conversations-container__conversations-list').first();
|
|
223
|
+
const listCount = await conversationsList.count();
|
|
224
|
+
console.log('[GetNewMessages] Conversations list count:', listCount);
|
|
225
|
+
|
|
226
|
+
if (listCount === 0) {
|
|
227
|
+
console.log('[GetNewMessages] No conversations list found, exiting');
|
|
228
|
+
try {
|
|
229
|
+
await page.unroute('**/voyagerMessagingGraphQL/**', routeHandler);
|
|
230
|
+
} catch (unrouteError) {
|
|
231
|
+
console.log('[GetNewMessages] Error unrouting (non-critical):', unrouteError.message);
|
|
232
|
+
}
|
|
233
|
+
// ⚠️ STOPPING POINT #2: No conversations found
|
|
234
|
+
return { success: false, items: null };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
console.log('[GetNewMessages] Waiting for conversation items to load...');
|
|
238
|
+
try {
|
|
239
|
+
await page.waitForSelector('li.msg-conversation-listitem .msg-conversation-listitem__link', {
|
|
240
|
+
timeout: 15000,
|
|
241
|
+
state: 'visible'
|
|
148
242
|
});
|
|
243
|
+
} catch (error) {
|
|
244
|
+
console.log('[GetNewMessages] No conversation items found');
|
|
245
|
+
try {
|
|
246
|
+
await page.unroute('**/voyagerMessagingGraphQL/**', routeHandler);
|
|
247
|
+
} catch (unrouteError) {
|
|
248
|
+
console.log('[GetNewMessages] Error unrouting (non-critical):', unrouteError.message);
|
|
249
|
+
}
|
|
250
|
+
// ⚠️ STOPPING POINT #3: No conversation items found
|
|
251
|
+
return { success: false, items: null };
|
|
252
|
+
}
|
|
149
253
|
|
|
150
|
-
|
|
151
|
-
|
|
254
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
255
|
+
const conversationItems = await conversationsList.locator('li.msg-conversation-listitem .msg-conversation-listitem__link').all();
|
|
256
|
+
console.log('[GetNewMessages] Found', conversationItems.length, 'conversation items');
|
|
257
|
+
|
|
258
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
259
|
+
return { success: true, items: conversationItems };
|
|
260
|
+
}
|
|
152
261
|
|
|
153
|
-
|
|
262
|
+
/**
|
|
263
|
+
* Scroll up in message container to load older messages
|
|
264
|
+
* Returns true if reached old messages (STOPPING POINT #4)
|
|
265
|
+
*/
|
|
266
|
+
async function scrollToLoadOldMessages(page, messageContainer, allMessageEntities, lastCheckedTimestamp) {
|
|
267
|
+
let reachedOldMessages = false;
|
|
268
|
+
let scrollAttempts = 0;
|
|
269
|
+
const maxScrollAttempts = 20;
|
|
270
|
+
|
|
271
|
+
await messageContainer.hover();
|
|
272
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
273
|
+
|
|
274
|
+
let noNewMessagesRetries = 0;
|
|
275
|
+
|
|
276
|
+
while (!reachedOldMessages && scrollAttempts < maxScrollAttempts) {
|
|
277
|
+
const beforeScrollCount = allMessageEntities.length;
|
|
278
|
+
console.log(`[GetNewMessages] Scroll cycle ${scrollAttempts + 1}, current messages: ${beforeScrollCount}`);
|
|
154
279
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
280
|
+
|
|
281
|
+
for (let i = 0; i < 5; i++) {
|
|
282
|
+
await messageContainer.evaluate(function(el) {
|
|
283
|
+
el.scrollBy({ top: -2000, behavior: 'smooth' });
|
|
159
284
|
});
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
285
|
+
console.log(`[GetNewMessages] Scrolled up -2000px (${i + 1}/5)`);
|
|
286
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
console.log(`[GetNewMessages] Waiting for LinkedIn to load older messages...`);
|
|
290
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
291
|
+
|
|
292
|
+
const afterScrollCount = allMessageEntities.length;
|
|
293
|
+
const newMessagesLoaded = afterScrollCount - beforeScrollCount;
|
|
294
|
+
console.log(`[GetNewMessages] After 5 scrolls: ${afterScrollCount} messages (${newMessagesLoaded} new)`);
|
|
295
|
+
|
|
296
|
+
if (newMessagesLoaded === 0) {
|
|
297
|
+
noNewMessagesRetries++;
|
|
298
|
+
console.log(`[GetNewMessages] No new messages loaded, retry ${noNewMessagesRetries}/2`);
|
|
299
|
+
|
|
300
|
+
if (noNewMessagesRetries >= 2) {
|
|
301
|
+
console.log('[GetNewMessages] No new messages after 2 retries, reached the top');
|
|
302
|
+
break;
|
|
166
303
|
}
|
|
167
|
-
|
|
304
|
+
} else {
|
|
305
|
+
noNewMessagesRetries = 0;
|
|
306
|
+
console.log(`[GetNewMessages] Loaded ${newMessagesLoaded} new messages, continuing...`);
|
|
168
307
|
}
|
|
169
308
|
|
|
170
|
-
|
|
309
|
+
const oldestMessage = allMessageEntities
|
|
310
|
+
.filter(msg => msg.deliveredAt)
|
|
311
|
+
.sort((a, b) => a.deliveredAt - b.deliveredAt)[0];
|
|
312
|
+
|
|
313
|
+
// ⚠️ STOPPING POINT #4: Check if reached messages older than lastChecked
|
|
314
|
+
if (oldestMessage && oldestMessage.deliveredAt < lastCheckedTimestamp) {
|
|
315
|
+
reachedOldMessages = true;
|
|
316
|
+
console.log('[GetNewMessages] Reached messages older than lastChecked in this conversation');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
scrollAttempts++;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
console.log(`[GetNewMessages] Finished scrolling after ${scrollAttempts} attempts`);
|
|
323
|
+
return reachedOldMessages;
|
|
324
|
+
}
|
|
171
325
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
326
|
+
/**
|
|
327
|
+
* Process a single conversation: click, scroll, and collect messages
|
|
328
|
+
* Returns shouldContinue flag (STOPPING POINT #5)
|
|
329
|
+
*/
|
|
330
|
+
async function processConversation(page, item, index, allMessageEntities, lastCheckedTimestamp) {
|
|
331
|
+
const messageCountBefore = allMessageEntities.length;
|
|
332
|
+
|
|
333
|
+
await item.scrollIntoViewIfNeeded();
|
|
334
|
+
await new Promise(resolve => setTimeout(resolve, 300));
|
|
335
|
+
await item.click({ timeout: 10000, force: false });
|
|
336
|
+
await new Promise(resolve => setTimeout(resolve, 2500));
|
|
337
|
+
|
|
338
|
+
const messageContainer = await page.locator('.msg-s-message-list.scrollable').first();
|
|
339
|
+
const containerExists = await messageContainer.count();
|
|
340
|
+
|
|
341
|
+
if (containerExists > 0) {
|
|
342
|
+
await scrollToLoadOldMessages(page, messageContainer, allMessageEntities, lastCheckedTimestamp);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const messageCountAfter = allMessageEntities.length;
|
|
346
|
+
const newMessages = messageCountAfter - messageCountBefore;
|
|
347
|
+
console.log(`[GetNewMessages] Total messages from conversation ${index + 1}: ${newMessages}`);
|
|
348
|
+
|
|
349
|
+
let shouldContinue = true;
|
|
350
|
+
if (newMessages > 0) {
|
|
351
|
+
const conversationMessages = allMessageEntities.slice(messageCountBefore);
|
|
352
|
+
const latestInThisConversation = conversationMessages
|
|
353
|
+
.filter(msg => msg.deliveredAt)
|
|
354
|
+
.sort((a, b) => b.deliveredAt - a.deliveredAt)[0];
|
|
176
355
|
|
|
177
|
-
if
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
356
|
+
// ⚠️ STOPPING POINT #5: Stop processing if latest message is older than lastChecked
|
|
357
|
+
if (latestInThisConversation && latestInThisConversation.deliveredAt < lastCheckedTimestamp) {
|
|
358
|
+
console.log('[GetNewMessages] Latest message in conversation is older than lastChecked, stopping conversation loop');
|
|
359
|
+
shouldContinue = false;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return shouldContinue;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Filter and organize new messages by conversation
|
|
368
|
+
*/
|
|
369
|
+
function filterAndOrganizeNewMessages(allMessageEntities, lastCheckedTimestamp) {
|
|
370
|
+
const newMessages = allMessageEntities.filter(msg => msg.deliveredAt > lastCheckedTimestamp);
|
|
371
|
+
newMessages.sort((a, b) => a.deliveredAt - b.deliveredAt);
|
|
372
|
+
console.log('[GetNewMessages] New messages count:', newMessages.length);
|
|
373
|
+
|
|
374
|
+
const newMessagesByConversation = {};
|
|
375
|
+
newMessages.forEach(msg => {
|
|
376
|
+
if (!newMessagesByConversation[msg.conversationId]) {
|
|
377
|
+
newMessagesByConversation[msg.conversationId] = [];
|
|
378
|
+
}
|
|
379
|
+
newMessagesByConversation[msg.conversationId].push(msg);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
console.log('[GetNewMessages] Conversations with new messages:', Object.keys(newMessagesByConversation).length);
|
|
383
|
+
return { newMessages, newMessagesByConversation };
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Build map of encoded URLs that need to be decoded
|
|
388
|
+
*/
|
|
389
|
+
function buildEncodedUrlMap(newMessagesByConversation, conversationParticipantsMap) {
|
|
390
|
+
const encodedUrlMap = new Map();
|
|
391
|
+
for (const conversationId in newMessagesByConversation) {
|
|
392
|
+
const encodedUrl = conversationParticipantsMap.get(conversationId);
|
|
393
|
+
if (encodedUrl) {
|
|
394
|
+
encodedUrlMap.set(encodedUrl, null);
|
|
395
|
+
} else {
|
|
396
|
+
console.log(`[GetNewMessages] No participant found for conversation:`, conversationId);
|
|
185
397
|
}
|
|
398
|
+
}
|
|
399
|
+
console.log('[GetNewMessages] Unique profiles to decode:', encodedUrlMap.size);
|
|
400
|
+
return encodedUrlMap;
|
|
401
|
+
}
|
|
186
402
|
|
|
187
|
-
|
|
403
|
+
/**
|
|
404
|
+
* Decode encoded LinkedIn profile URLs by visiting them
|
|
405
|
+
*/
|
|
406
|
+
async function decodeProfileUrls(page, encodedUrlMap) {
|
|
407
|
+
const decodedUrlMap = new Map();
|
|
408
|
+
const maxDecodeAttempts = Math.min(encodedUrlMap.size, 50);
|
|
409
|
+
let decodeCount = 0;
|
|
410
|
+
|
|
411
|
+
for (const encodedUrl of encodedUrlMap.keys()) {
|
|
412
|
+
if (decodeCount >= maxDecodeAttempts) {
|
|
413
|
+
console.log(`[GetNewMessages] Reached max decode attempts (${maxDecodeAttempts}), skipping remaining profiles`);
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
|
|
188
417
|
try {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
418
|
+
console.log(`[GetNewMessages] Decoding profile ${decodeCount + 1}/${encodedUrlMap.size}: ${encodedUrl}`);
|
|
419
|
+
|
|
420
|
+
const decodePromise = safeGoto(page, `https://www.linkedin.com/in/${encodedUrl}`, {
|
|
421
|
+
waitUntil: 'domcontentloaded',
|
|
422
|
+
timeout: 30000
|
|
192
423
|
});
|
|
424
|
+
|
|
425
|
+
const timeoutPromise = new Promise((_, reject) =>
|
|
426
|
+
setTimeout(() => reject(new Error('Decode timeout')), 35000)
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
await Promise.race([decodePromise, timeoutPromise]);
|
|
430
|
+
await page.waitForLoadState('domcontentloaded', { timeout: 10000 });
|
|
431
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
432
|
+
|
|
433
|
+
const currentUrl = page.url();
|
|
434
|
+
const match = currentUrl.match(/linkedin\.com\/in\/([^\/\?]+)/);
|
|
435
|
+
if (match && match[1]) {
|
|
436
|
+
decodedUrlMap.set(encodedUrl, match[1]);
|
|
437
|
+
console.log(`[GetNewMessages] Successfully decoded: ${encodedUrl} -> ${match[1]}`);
|
|
438
|
+
} else {
|
|
439
|
+
console.log(`[GetNewMessages] Could not extract URL from: ${currentUrl}`);
|
|
440
|
+
}
|
|
441
|
+
decodeCount++;
|
|
193
442
|
} catch (error) {
|
|
194
|
-
console.
|
|
443
|
+
console.error(`[GetNewMessages] Failed to decode URL ${encodedUrl}:`, error.message);
|
|
444
|
+
decodeCount++;
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return decodedUrlMap;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* ⚠️ SENDING FUNCTIONALITY: Send messages to API endpoint
|
|
454
|
+
* This is the core functionality for sending collected messages to the backend
|
|
455
|
+
*/
|
|
456
|
+
async function sendMessagesToApi(newMessagesByConversation, conversationParticipantsMap, decodedUrlMap, accessToken) {
|
|
457
|
+
let sentCount = 0;
|
|
458
|
+
let failedCount = 0;
|
|
459
|
+
let totalToSend = 0;
|
|
460
|
+
|
|
461
|
+
for (const conversationId in newMessagesByConversation) {
|
|
462
|
+
const messages = newMessagesByConversation[conversationId];
|
|
463
|
+
const encodedUrl = conversationParticipantsMap.get(conversationId);
|
|
464
|
+
const linkedinUrl = encodedUrl ? decodedUrlMap.get(encodedUrl) : null;
|
|
465
|
+
|
|
466
|
+
if (!linkedinUrl) {
|
|
467
|
+
console.log(`[GetNewMessages] Skipping conversation ${conversationId} - could not find or decode other person's URL`);
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
console.log(`[GetNewMessages] Sending ${messages.length} messages to ${linkedinUrl}`);
|
|
472
|
+
totalToSend += messages.length;
|
|
473
|
+
|
|
474
|
+
for (const msg of messages) {
|
|
195
475
|
try {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
476
|
+
const payload = {
|
|
477
|
+
linkedin_url: linkedinUrl,
|
|
478
|
+
message: msg.messageText,
|
|
479
|
+
is_receipent: !msg.isSelf,
|
|
480
|
+
timestamp: msg.deliveredAt
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
console.log(`[GetNewMessages] Posting message: "${msg.messageText.substring(0, 50)}..." (${msg.deliveredAt})`);
|
|
484
|
+
|
|
485
|
+
await axios.post('https://api.coffeeinabit.com/messages', payload, {
|
|
486
|
+
headers: {
|
|
487
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
488
|
+
'Content-Type': 'application/json'
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
sentCount++;
|
|
493
|
+
} catch (error) {
|
|
494
|
+
failedCount++;
|
|
495
|
+
console.error(`[GetNewMessages] Failed to send message to ${linkedinUrl}: "${msg.messageText.substring(0, 50)}..."`);
|
|
496
|
+
console.error(`[GetNewMessages] Error:`, error.message);
|
|
497
|
+
if (error.response) {
|
|
498
|
+
console.error(`[GetNewMessages] Response status:`, error.response.status);
|
|
499
|
+
console.error(`[GetNewMessages] Response data:`, error.response.data);
|
|
500
|
+
}
|
|
199
501
|
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
console.log(`[GetNewMessages] Successfully sent ${sentCount}/${totalToSend} messages${failedCount > 0 ? ` (${failedCount} failed)` : ''}`);
|
|
506
|
+
return { sentCount, failedCount, totalToSend };
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Cleanup route handlers
|
|
511
|
+
*/
|
|
512
|
+
async function cleanupRouteHandlers(page, routeHandler) {
|
|
513
|
+
console.log('[GetNewMessages] Unrouting message handlers...');
|
|
514
|
+
try {
|
|
515
|
+
await page.unroute('**/voyagerMessagingGraphQL/**', routeHandler);
|
|
516
|
+
console.log('[GetNewMessages] Successfully unrouted message handlers');
|
|
517
|
+
} catch (error) {
|
|
518
|
+
console.log('[GetNewMessages] Error unrouting (non-critical):', error.message);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Main function to get new messages from LinkedIn
|
|
524
|
+
*/
|
|
525
|
+
export async function executeGetNewMessages(page, action, accessToken) {
|
|
526
|
+
const lastCheckedTimestamp = parseLastCheckedTimestamp(action);
|
|
527
|
+
|
|
528
|
+
const allMessageEntities = [];
|
|
529
|
+
const processedMessages = new Set();
|
|
530
|
+
const conversationParticipantsMap = new Map();
|
|
531
|
+
|
|
532
|
+
const routeHandler = createRouteHandler(allMessageEntities, processedMessages, conversationParticipantsMap);
|
|
533
|
+
|
|
534
|
+
try {
|
|
535
|
+
await page.route('**/voyagerMessagingGraphQL/**', routeHandler);
|
|
536
|
+
|
|
537
|
+
await navigateToMessagingPage(page);
|
|
538
|
+
|
|
539
|
+
const conversationsResult = await waitForConversationsList(page, routeHandler);
|
|
540
|
+
if (!conversationsResult.success) {
|
|
200
541
|
return { newMessagesCount: 0, messages: [] };
|
|
201
542
|
}
|
|
202
543
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
544
|
+
const itemsResult = await getConversationItems(page, routeHandler);
|
|
545
|
+
if (!itemsResult.success) {
|
|
546
|
+
return { newMessagesCount: 0, messages: [] };
|
|
547
|
+
}
|
|
206
548
|
|
|
207
|
-
|
|
549
|
+
const conversationItems = itemsResult.items;
|
|
208
550
|
const initialMessageCount = allMessageEntities.length;
|
|
209
551
|
console.log(`[GetNewMessages] Messages from initially opened conversation: ${initialMessageCount}`);
|
|
210
552
|
|
|
@@ -213,89 +555,7 @@ export async function executeGetNewMessages(page, action, accessToken) {
|
|
|
213
555
|
const item = conversationItems[i];
|
|
214
556
|
|
|
215
557
|
try {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
await item.scrollIntoViewIfNeeded();
|
|
219
|
-
await new Promise(resolve => setTimeout(resolve, 300));
|
|
220
|
-
await item.click({ timeout: 10000, force: false });
|
|
221
|
-
await new Promise(resolve => setTimeout(resolve, 2500));
|
|
222
|
-
|
|
223
|
-
const messageContainer = await page.locator('.msg-s-message-list.scrollable').first();
|
|
224
|
-
const containerExists = await messageContainer.count();
|
|
225
|
-
|
|
226
|
-
if (containerExists > 0) {
|
|
227
|
-
let reachedOldMessages = false;
|
|
228
|
-
let scrollAttempts = 0;
|
|
229
|
-
const maxScrollAttempts = 20;
|
|
230
|
-
|
|
231
|
-
await messageContainer.hover();
|
|
232
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
233
|
-
|
|
234
|
-
let noNewMessagesRetries = 0;
|
|
235
|
-
|
|
236
|
-
while (!reachedOldMessages && scrollAttempts < maxScrollAttempts) {
|
|
237
|
-
const beforeScrollCount = allMessageEntities.length;
|
|
238
|
-
console.log(`[GetNewMessages] Scroll cycle ${scrollAttempts + 1}, current messages: ${beforeScrollCount}`);
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
for (let i = 0; i < 5; i++) {
|
|
242
|
-
await messageContainer.evaluate((el) => {
|
|
243
|
-
el.scrollBy({ top: -2000, behavior: 'smooth' });
|
|
244
|
-
});
|
|
245
|
-
console.log(`[GetNewMessages] Scrolled up -2000px (${i + 1}/5)`);
|
|
246
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
console.log(`[GetNewMessages] Waiting for LinkedIn to load older messages...`);
|
|
250
|
-
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
251
|
-
|
|
252
|
-
const afterScrollCount = allMessageEntities.length;
|
|
253
|
-
const newMessagesLoaded = afterScrollCount - beforeScrollCount;
|
|
254
|
-
console.log(`[GetNewMessages] After 5 scrolls: ${afterScrollCount} messages (${newMessagesLoaded} new)`);
|
|
255
|
-
|
|
256
|
-
if (newMessagesLoaded === 0) {
|
|
257
|
-
noNewMessagesRetries++;
|
|
258
|
-
console.log(`[GetNewMessages] No new messages loaded, retry ${noNewMessagesRetries}/2`);
|
|
259
|
-
|
|
260
|
-
if (noNewMessagesRetries >= 2) {
|
|
261
|
-
console.log('[GetNewMessages] No new messages after 2 retries, reached the top');
|
|
262
|
-
break;
|
|
263
|
-
}
|
|
264
|
-
} else {
|
|
265
|
-
noNewMessagesRetries = 0;
|
|
266
|
-
console.log(`[GetNewMessages] Loaded ${newMessagesLoaded} new messages, continuing...`);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const oldestMessage = allMessageEntities
|
|
270
|
-
.filter(msg => msg.deliveredAt)
|
|
271
|
-
.sort((a, b) => a.deliveredAt - b.deliveredAt)[0];
|
|
272
|
-
|
|
273
|
-
if (oldestMessage && oldestMessage.deliveredAt < lastCheckedTimestamp) {
|
|
274
|
-
reachedOldMessages = true;
|
|
275
|
-
console.log('[GetNewMessages] Reached messages older than lastChecked in this conversation');
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
scrollAttempts++;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
console.log(`[GetNewMessages] Finished scrolling after ${scrollAttempts} attempts`);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const messageCountAfter = allMessageEntities.length;
|
|
285
|
-
const newMessages = messageCountAfter - messageCountBefore;
|
|
286
|
-
console.log(`[GetNewMessages] Total messages from conversation ${i + 1}: ${newMessages}`);
|
|
287
|
-
|
|
288
|
-
if (newMessages > 0) {
|
|
289
|
-
const conversationMessages = allMessageEntities.slice(messageCountBefore);
|
|
290
|
-
const latestInThisConversation = conversationMessages
|
|
291
|
-
.filter(msg => msg.deliveredAt)
|
|
292
|
-
.sort((a, b) => b.deliveredAt - a.deliveredAt)[0];
|
|
293
|
-
|
|
294
|
-
if (latestInThisConversation && latestInThisConversation.deliveredAt < lastCheckedTimestamp) {
|
|
295
|
-
console.log('[GetNewMessages] Latest message in conversation is older than lastChecked, stopping conversation loop');
|
|
296
|
-
shouldContinue = false;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
558
|
+
shouldContinue = await processConversation(page, item, i, allMessageEntities, lastCheckedTimestamp);
|
|
299
559
|
} catch (error) {
|
|
300
560
|
console.error('[GetNewMessages] Error with conversation:', error.message);
|
|
301
561
|
}
|
|
@@ -304,133 +564,18 @@ export async function executeGetNewMessages(page, action, accessToken) {
|
|
|
304
564
|
console.log('[GetNewMessages] Finished processing all conversations, waiting before cleanup...');
|
|
305
565
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
306
566
|
|
|
307
|
-
|
|
308
|
-
try {
|
|
309
|
-
await page.unroute('**/voyagerMessagingGraphQL/**', routeHandler);
|
|
310
|
-
console.log('[GetNewMessages] Successfully unrouted message handlers');
|
|
311
|
-
} catch (error) {
|
|
312
|
-
console.log('[GetNewMessages] Error unrouting (non-critical):', error.message);
|
|
313
|
-
}
|
|
567
|
+
await cleanupRouteHandlers(page, routeHandler);
|
|
314
568
|
|
|
315
569
|
console.log('[GetNewMessages] Total messages collected:', allMessageEntities.length);
|
|
316
570
|
console.log('[GetNewMessages] Conversations with participants mapped:', conversationParticipantsMap.size);
|
|
317
571
|
|
|
318
|
-
const newMessages = allMessageEntities
|
|
319
|
-
newMessages.sort((a, b) => a.deliveredAt - b.deliveredAt);
|
|
320
|
-
console.log('[GetNewMessages] New messages count:', newMessages.length);
|
|
321
|
-
|
|
322
|
-
const newMessagesByConversation = {};
|
|
323
|
-
newMessages.forEach(msg => {
|
|
324
|
-
if (!newMessagesByConversation[msg.conversationId]) {
|
|
325
|
-
newMessagesByConversation[msg.conversationId] = [];
|
|
326
|
-
}
|
|
327
|
-
newMessagesByConversation[msg.conversationId].push(msg);
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
console.log('[GetNewMessages] Conversations with new messages:', Object.keys(newMessagesByConversation).length);
|
|
331
|
-
|
|
332
|
-
const encodedUrlMap = new Map();
|
|
333
|
-
for (const conversationId in newMessagesByConversation) {
|
|
334
|
-
const encodedUrl = conversationParticipantsMap.get(conversationId);
|
|
335
|
-
if (encodedUrl) {
|
|
336
|
-
encodedUrlMap.set(encodedUrl, null);
|
|
337
|
-
} else {
|
|
338
|
-
console.log(`[GetNewMessages] No participant found for conversation:`, conversationId);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
console.log('[GetNewMessages] Unique profiles to decode:', encodedUrlMap.size);
|
|
572
|
+
const { newMessages, newMessagesByConversation } = filterAndOrganizeNewMessages(allMessageEntities, lastCheckedTimestamp);
|
|
342
573
|
|
|
343
|
-
const
|
|
344
|
-
const
|
|
345
|
-
let decodeCount = 0;
|
|
574
|
+
const encodedUrlMap = buildEncodedUrlMap(newMessagesByConversation, conversationParticipantsMap);
|
|
575
|
+
const decodedUrlMap = await decodeProfileUrls(page, encodedUrlMap);
|
|
346
576
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
console.log(`[GetNewMessages] Reached max decode attempts (${maxDecodeAttempts}), skipping remaining profiles`);
|
|
350
|
-
break;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
try {
|
|
354
|
-
console.log(`[GetNewMessages] Decoding profile ${decodeCount + 1}/${encodedUrlMap.size}: ${encodedUrl}`);
|
|
355
|
-
|
|
356
|
-
const decodePromise = safeGoto(page, `https://www.linkedin.com/in/${encodedUrl}`, {
|
|
357
|
-
waitUntil: 'domcontentloaded',
|
|
358
|
-
timeout: 30000
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
const timeoutPromise = new Promise((_, reject) =>
|
|
362
|
-
setTimeout(() => reject(new Error('Decode timeout')), 35000)
|
|
363
|
-
);
|
|
364
|
-
|
|
365
|
-
await Promise.race([decodePromise, timeoutPromise]);
|
|
366
|
-
await page.waitForLoadState('domcontentloaded', { timeout: 10000 });
|
|
367
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
368
|
-
|
|
369
|
-
const currentUrl = page.url();
|
|
370
|
-
const match = currentUrl.match(/linkedin\.com\/in\/([^\/\?]+)/);
|
|
371
|
-
if (match && match[1]) {
|
|
372
|
-
decodedUrlMap.set(encodedUrl, match[1]);
|
|
373
|
-
console.log(`[GetNewMessages] Successfully decoded: ${encodedUrl} -> ${match[1]}`);
|
|
374
|
-
} else {
|
|
375
|
-
console.log(`[GetNewMessages] Could not extract URL from: ${currentUrl}`);
|
|
376
|
-
}
|
|
377
|
-
decodeCount++;
|
|
378
|
-
} catch (error) {
|
|
379
|
-
console.error(`[GetNewMessages] Failed to decode URL ${encodedUrl}:`, error.message);
|
|
380
|
-
decodeCount++;
|
|
381
|
-
continue;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
let sentCount = 0;
|
|
386
|
-
let failedCount = 0;
|
|
387
|
-
let totalToSend = 0;
|
|
388
|
-
|
|
389
|
-
for (const conversationId in newMessagesByConversation) {
|
|
390
|
-
const messages = newMessagesByConversation[conversationId];
|
|
391
|
-
const encodedUrl = conversationParticipantsMap.get(conversationId);
|
|
392
|
-
const linkedinUrl = encodedUrl ? decodedUrlMap.get(encodedUrl) : null;
|
|
393
|
-
|
|
394
|
-
if (!linkedinUrl) {
|
|
395
|
-
console.log(`[GetNewMessages] Skipping conversation ${conversationId} - could not find or decode other person's URL`);
|
|
396
|
-
continue;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
console.log(`[GetNewMessages] Sending ${messages.length} messages to ${linkedinUrl}`);
|
|
400
|
-
totalToSend += messages.length;
|
|
401
|
-
|
|
402
|
-
for (const msg of messages) {
|
|
403
|
-
try {
|
|
404
|
-
const payload = {
|
|
405
|
-
linkedin_url: linkedinUrl,
|
|
406
|
-
message: msg.messageText,
|
|
407
|
-
is_receipent: !msg.isSelf,
|
|
408
|
-
timestamp: msg.deliveredAt
|
|
409
|
-
};
|
|
410
|
-
|
|
411
|
-
console.log(`[GetNewMessages] Posting message: "${msg.messageText.substring(0, 50)}..." (${msg.deliveredAt})`);
|
|
412
|
-
|
|
413
|
-
await axios.post('https://api.coffeeinabit.com/messages', payload, {
|
|
414
|
-
headers: {
|
|
415
|
-
'Authorization': `Bearer ${accessToken}`,
|
|
416
|
-
'Content-Type': 'application/json'
|
|
417
|
-
}
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
sentCount++;
|
|
421
|
-
} catch (error) {
|
|
422
|
-
failedCount++;
|
|
423
|
-
console.error(`[GetNewMessages] Failed to send message to ${linkedinUrl}: "${msg.messageText.substring(0, 50)}..."`);
|
|
424
|
-
console.error(`[GetNewMessages] Error:`, error.message);
|
|
425
|
-
if (error.response) {
|
|
426
|
-
console.error(`[GetNewMessages] Response status:`, error.response.status);
|
|
427
|
-
console.error(`[GetNewMessages] Response data:`, error.response.data);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
console.log(`[GetNewMessages] Successfully sent ${sentCount}/${totalToSend} messages${failedCount > 0 ? ` (${failedCount} failed)` : ''}`);
|
|
577
|
+
// ⚠️ SENDING FUNCTIONALITY: Send all collected messages to API
|
|
578
|
+
await sendMessagesToApi(newMessagesByConversation, conversationParticipantsMap, decodedUrlMap, accessToken);
|
|
434
579
|
|
|
435
580
|
return {
|
|
436
581
|
newMessagesCount: newMessages.length,
|