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,12 +1,32 @@
|
|
|
1
1
|
import { safeGoto } from './navigation.js';
|
|
2
2
|
|
|
3
|
+
const CONNECTIONS_URL = 'https://www.linkedin.com/mynetwork/invite-connect/connections/';
|
|
4
|
+
const MAX_SCROLL_PAGES = 50;
|
|
5
|
+
const MAX_NO_SCROLL_ATTEMPTS = 2;
|
|
6
|
+
const SCROLL_AMOUNT = 2000;
|
|
7
|
+
const SCROLL_WAIT_TIME = 3000;
|
|
8
|
+
const RETRY_WAIT_TIME = 5000;
|
|
9
|
+
|
|
3
10
|
export async function executeGetDailyLinkedInConnections(page, action) {
|
|
4
|
-
const lastCheckTimestamp = action
|
|
11
|
+
const lastCheckTimestamp = extractLastCheckTimestamp(action);
|
|
12
|
+
const now = Date.now();
|
|
13
|
+
|
|
14
|
+
await navigateToConnectionsPage(page);
|
|
15
|
+
const totalConnections = await extractTotalConnections(page);
|
|
16
|
+
const allConnections = await collectAllConnections(page, lastCheckTimestamp);
|
|
17
|
+
const newConnections = filterNewConnections(allConnections, lastCheckTimestamp);
|
|
18
|
+
|
|
19
|
+
return createResult(totalConnections, newConnections, lastCheckTimestamp, now);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function extractLastCheckTimestamp(action) {
|
|
23
|
+
return action.parameters?.last_check_timestamp
|
|
5
24
|
? parseInt(action.parameters.last_check_timestamp, 10)
|
|
6
25
|
: null;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function navigateToConnectionsPage(page) {
|
|
29
|
+
await safeGoto(page, CONNECTIONS_URL, {
|
|
10
30
|
waitUntil: 'domcontentloaded',
|
|
11
31
|
timeout: 60000
|
|
12
32
|
});
|
|
@@ -16,101 +36,142 @@ export async function executeGetDailyLinkedInConnections(page, action) {
|
|
|
16
36
|
});
|
|
17
37
|
|
|
18
38
|
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 5000) + 500));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function collectAllConnections(page, lastCheckTimestamp) {
|
|
42
|
+
const allConnectionsMap = new Map();
|
|
43
|
+
const scrollState = {
|
|
44
|
+
scrollPageCount: 0,
|
|
45
|
+
noScrollAttempts: 0,
|
|
46
|
+
shouldStop: false
|
|
47
|
+
};
|
|
19
48
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const now = Date.now();
|
|
23
|
-
let allConnectionsMap = new Map();
|
|
24
|
-
let shouldStopScrolling = false;
|
|
25
|
-
let noScrollAttempts = 0;
|
|
26
|
-
const maxNoScrollAttempts = 2;
|
|
27
|
-
|
|
28
|
-
while (!shouldStopScrolling) {
|
|
29
|
-
const currentConnections = await extractCurrentConnections(page);
|
|
49
|
+
while (!scrollState.shouldStop) {
|
|
50
|
+
scrollState.scrollPageCount++;
|
|
30
51
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
52
|
+
if (scrollState.scrollPageCount > MAX_SCROLL_PAGES) {
|
|
53
|
+
console.log(`[get_daily_linkedin_connections] Reached max scroll pages limit (${MAX_SCROLL_PAGES}), stopping`);
|
|
54
|
+
scrollState.shouldStop = true;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log(`[get_daily_linkedin_connections] Scroll page ${scrollState.scrollPageCount}/${MAX_SCROLL_PAGES}`);
|
|
59
|
+
|
|
60
|
+
const currentConnections = await extractCurrentConnections(page);
|
|
61
|
+
addConnectionsToMap(allConnectionsMap, currentConnections);
|
|
34
62
|
|
|
35
63
|
console.log(`[get_daily_linkedin_connections] Current page connections: ${currentConnections.length}, Total unique: ${allConnectionsMap.size}`);
|
|
36
64
|
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return current.connectionTimestamp < oldest.connectionTimestamp ? current : oldest;
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
if (oldestConnection.connectionTimestamp && oldestConnection.connectionTimestamp < lastCheckTimestamp) {
|
|
45
|
-
console.log('[get_daily_linkedin_connections] Reached connections older than last check, stopping');
|
|
46
|
-
shouldStopScrolling = true;
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
65
|
+
if (shouldStopDueToOldConnections(currentConnections, lastCheckTimestamp)) {
|
|
66
|
+
console.log('[get_daily_linkedin_connections] Reached connections older than last check, stopping');
|
|
67
|
+
scrollState.shouldStop = true;
|
|
68
|
+
break;
|
|
49
69
|
}
|
|
50
70
|
|
|
51
|
-
const
|
|
71
|
+
const scrollProgress = await performScrollIteration(page);
|
|
72
|
+
await updateScrollState(scrollState, scrollProgress);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return Array.from(allConnectionsMap.values());
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function addConnectionsToMap(connectionsMap, connections) {
|
|
79
|
+
connections.forEach(conn => {
|
|
80
|
+
connectionsMap.set(conn.username, conn);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function shouldStopDueToOldConnections(currentConnections, lastCheckTimestamp) {
|
|
85
|
+
if (!lastCheckTimestamp || currentConnections.length === 0) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const oldestConnection = currentConnections.reduce((oldest, current) => {
|
|
90
|
+
if (!oldest.connectionTimestamp) return current;
|
|
91
|
+
if (!current.connectionTimestamp) return oldest;
|
|
92
|
+
return current.connectionTimestamp < oldest.connectionTimestamp ? current : oldest;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return oldestConnection.connectionTimestamp &&
|
|
96
|
+
oldestConnection.connectionTimestamp < lastCheckTimestamp;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function performScrollIteration(page) {
|
|
100
|
+
const scrollInfoBefore = await getScrollInfo(page);
|
|
101
|
+
console.log(`[get_daily_linkedin_connections] Before scroll - Top: ${scrollInfoBefore.scrollTop}px, Height: ${scrollInfoBefore.scrollHeight}px`);
|
|
102
|
+
|
|
103
|
+
await performScroll(page);
|
|
104
|
+
|
|
105
|
+
const scrollInfoAfter = await getScrollInfo(page);
|
|
106
|
+
console.log(`[get_daily_linkedin_connections] After scroll - Top: ${scrollInfoAfter.scrollTop}px, Height: ${scrollInfoAfter.scrollHeight}px`);
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
scrolled: scrollInfoAfter.scrollTop > scrollInfoBefore.scrollTop,
|
|
110
|
+
heightIncreased: scrollInfoAfter.scrollHeight > scrollInfoBefore.scrollHeight
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function performScroll(page) {
|
|
115
|
+
try {
|
|
116
|
+
console.log('[get_daily_linkedin_connections] Scrolling main element...');
|
|
52
117
|
|
|
53
|
-
|
|
118
|
+
const mainElement = await page.locator('main').first();
|
|
119
|
+
const containerExists = await mainElement.count();
|
|
54
120
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if (containerExists > 0) {
|
|
62
|
-
await mainElement.evaluate(function(el) {
|
|
63
|
-
console.log('Main element found, scrolling...');
|
|
64
|
-
console.log('Main scrollHeight:', el.scrollHeight);
|
|
65
|
-
console.log('Main clientHeight:', el.clientHeight);
|
|
66
|
-
console.log('Main scrollTop before:', el.scrollTop);
|
|
67
|
-
|
|
68
|
-
el.scrollBy({ top: 2000, behavior: 'smooth' });
|
|
69
|
-
|
|
70
|
-
console.log('Main scrollTop after:', el.scrollTop);
|
|
71
|
-
});
|
|
121
|
+
if (containerExists > 0) {
|
|
122
|
+
await mainElement.evaluate(function(el) {
|
|
123
|
+
console.log('Main element found, scrolling...');
|
|
124
|
+
console.log('Main scrollHeight:', el.scrollHeight);
|
|
125
|
+
console.log('Main clientHeight:', el.clientHeight);
|
|
126
|
+
console.log('Main scrollTop before:', el.scrollTop);
|
|
72
127
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
console.log('
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
console.log(
|
|
81
|
-
console.log('Stack trace:', e.stack);
|
|
128
|
+
el.scrollBy({ top: SCROLL_AMOUNT, behavior: 'smooth' });
|
|
129
|
+
|
|
130
|
+
console.log('Main scrollTop after:', el.scrollTop);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
console.log('[get_daily_linkedin_connections] Scroll completed');
|
|
134
|
+
} else {
|
|
135
|
+
console.log('[get_daily_linkedin_connections] Main element not found');
|
|
82
136
|
}
|
|
83
137
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
138
|
+
console.log('[get_daily_linkedin_connections] Waiting for content to load...');
|
|
139
|
+
await new Promise(resolve => setTimeout(resolve, SCROLL_WAIT_TIME));
|
|
140
|
+
} catch (e) {
|
|
141
|
+
console.log(`[get_daily_linkedin_connections] Scroll failed: ${e.message}`);
|
|
142
|
+
console.log('Stack trace:', e.stack);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function updateScrollState(scrollState, scrollProgress) {
|
|
147
|
+
if (scrollProgress.scrolled || scrollProgress.heightIncreased) {
|
|
148
|
+
console.log('[get_daily_linkedin_connections] Page scrolled or expanded, continuing...');
|
|
149
|
+
scrollState.noScrollAttempts = 0;
|
|
150
|
+
} else {
|
|
151
|
+
scrollState.noScrollAttempts++;
|
|
152
|
+
console.log(`[get_daily_linkedin_connections] No scroll detected (attempt ${scrollState.noScrollAttempts}/${MAX_NO_SCROLL_ATTEMPTS})`);
|
|
90
153
|
|
|
91
|
-
if (
|
|
92
|
-
console.log('[get_daily_linkedin_connections]
|
|
93
|
-
|
|
154
|
+
if (scrollState.noScrollAttempts >= MAX_NO_SCROLL_ATTEMPTS) {
|
|
155
|
+
console.log('[get_daily_linkedin_connections] Max attempts reached, stopping scroll');
|
|
156
|
+
scrollState.shouldStop = true;
|
|
94
157
|
} else {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (noScrollAttempts < maxNoScrollAttempts) {
|
|
99
|
-
console.log('[get_daily_linkedin_connections] Waiting 5 seconds and trying again...');
|
|
100
|
-
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
101
|
-
} else {
|
|
102
|
-
console.log('[get_daily_linkedin_connections] Max attempts reached, stopping scroll');
|
|
103
|
-
shouldStopScrolling = true;
|
|
104
|
-
}
|
|
158
|
+
console.log('[get_daily_linkedin_connections] Waiting 5 seconds and trying again...');
|
|
159
|
+
await new Promise(resolve => setTimeout(resolve, RETRY_WAIT_TIME));
|
|
105
160
|
}
|
|
106
161
|
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function filterNewConnections(allConnections, lastCheckTimestamp) {
|
|
165
|
+
if (!lastCheckTimestamp) {
|
|
166
|
+
return allConnections;
|
|
167
|
+
}
|
|
107
168
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
169
|
+
return allConnections.filter(conn =>
|
|
170
|
+
conn.connectionTimestamp && conn.connectionTimestamp >= lastCheckTimestamp
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function createResult(totalConnections, newConnections, lastCheckTimestamp, currentTimestamp) {
|
|
114
175
|
return {
|
|
115
176
|
action: 'get_daily_linkedin_connections',
|
|
116
177
|
result: {
|
|
@@ -118,7 +179,7 @@ export async function executeGetDailyLinkedInConnections(page, action) {
|
|
|
118
179
|
newConnections: newConnections,
|
|
119
180
|
newConnectionsCount: newConnections.length,
|
|
120
181
|
lastCheckTimestamp: lastCheckTimestamp,
|
|
121
|
-
currentTimestamp:
|
|
182
|
+
currentTimestamp: currentTimestamp
|
|
122
183
|
},
|
|
123
184
|
status: 'success'
|
|
124
185
|
};
|