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,32 @@
|
|
|
1
|
+
export async function humanLikeType(element, text) {
|
|
2
|
+
for (const char of text) {
|
|
3
|
+
await element.type(char, { delay: 0 });
|
|
4
|
+
|
|
5
|
+
const delay = Math.floor(Math.random() * 100) + 50;
|
|
6
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const finalDelay = Math.floor(Math.random() * 300) + 200;
|
|
10
|
+
await new Promise(resolve => setTimeout(resolve, finalDelay));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function readLinkedInCredentials() {
|
|
14
|
+
const fs = (await import('fs')).default;
|
|
15
|
+
const path = (await import('path')).default;
|
|
16
|
+
|
|
17
|
+
const credentialsPath = path.join(process.cwd(), 'credentials', 'linkedin.json');
|
|
18
|
+
|
|
19
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
20
|
+
throw new Error(`Credentials file not found at: ${credentialsPath}. Please create it with your LinkedIn email and password.`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const credentialsData = fs.readFileSync(credentialsPath, 'utf-8');
|
|
24
|
+
const credentials = JSON.parse(credentialsData);
|
|
25
|
+
|
|
26
|
+
if (!credentials.email || !credentials.password) {
|
|
27
|
+
throw new Error('Credentials file must contain "email" and "password" fields');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return credentials;
|
|
31
|
+
}
|
|
32
|
+
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export async function executeLikePost(page, action) {
|
|
2
|
+
const postUrl = action.parameters?.url || action.url;
|
|
3
|
+
|
|
4
|
+
if (!postUrl) {
|
|
5
|
+
throw new Error('Missing url for like_post');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
await page.goto(postUrl, {
|
|
9
|
+
waitUntil: 'domcontentloaded',
|
|
10
|
+
timeout: 60000
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
await page.waitForSelector('main', { timeout: Math.floor(Math.random() * 5000) + 5000 });
|
|
14
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 3000) + 500));
|
|
15
|
+
|
|
16
|
+
let likeButton = await page.locator('main button[aria-label="React Like"], main button[aria-label="Unreact Like"]').first();
|
|
17
|
+
|
|
18
|
+
if (await likeButton.count() === 0) {
|
|
19
|
+
likeButton = await page.locator('main button:has-text("Like"), main button:has-text("Unlike")').first();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (await likeButton.count() > 0) {
|
|
23
|
+
const ariaLabel = await likeButton.getAttribute('aria-label');
|
|
24
|
+
|
|
25
|
+
if (ariaLabel === 'Unreact Like') {
|
|
26
|
+
return {
|
|
27
|
+
action: 'like_post',
|
|
28
|
+
result: { status: 'already_liked', message: `Post is already liked: ${postUrl}` },
|
|
29
|
+
status: 'success'
|
|
30
|
+
};
|
|
31
|
+
} else if (ariaLabel === 'React Like') {
|
|
32
|
+
await page.evaluate((button) => {
|
|
33
|
+
if (button) button.click();
|
|
34
|
+
}, await likeButton.elementHandle());
|
|
35
|
+
|
|
36
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 2000) + 500));
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
action: 'like_post',
|
|
40
|
+
result: { status: 'liked', message: `Post liked successfully: ${postUrl}` },
|
|
41
|
+
status: 'success'
|
|
42
|
+
};
|
|
43
|
+
} else {
|
|
44
|
+
return {
|
|
45
|
+
action: 'like_post',
|
|
46
|
+
result: { status: 'unknown', message: `Could not determine Like button state for post: ${postUrl}` },
|
|
47
|
+
status: 'failed'
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
return {
|
|
52
|
+
action: 'like_post',
|
|
53
|
+
result: { status: 'not_found', message: `Could not find Like button for post: ${postUrl}` },
|
|
54
|
+
status: 'failed'
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
export async function executeSendConnectionRequest(page, action) {
|
|
2
|
+
let username = action.parameters?.username;
|
|
3
|
+
if (!username) {
|
|
4
|
+
throw new Error('Missing username for send_connection_request');
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
const profileUrl = `https://www.linkedin.com/in/${username}`;
|
|
9
|
+
await page.goto(profileUrl, {
|
|
10
|
+
waitUntil: 'load',
|
|
11
|
+
timeout: 60000
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
await page.waitForLoadState('load');
|
|
15
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
16
|
+
|
|
17
|
+
await page.evaluate(() => { window.scrollTo(0, 300); });
|
|
18
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 2000) + 500));
|
|
19
|
+
|
|
20
|
+
return await detectConnectionStatus(page, username);
|
|
21
|
+
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error('[SendConnectionRequest] Error:', error.message);
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function detectConnectionStatus(page, username) {
|
|
29
|
+
let inMain = true;
|
|
30
|
+
|
|
31
|
+
const connectResult = await handleConnectButtonClick(page, username, inMain, true);
|
|
32
|
+
if (connectResult.status === 'success') {
|
|
33
|
+
return connectResult;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let moreActionsSelectors = await getButtonByText(page, 'More', inMain);
|
|
37
|
+
if (!moreActionsSelectors || !(await moreActionsSelectors.isVisible())) {
|
|
38
|
+
moreActionsSelectors = await getButtonByText(page, 'More actions', inMain);
|
|
39
|
+
}
|
|
40
|
+
if (moreActionsSelectors && (await moreActionsSelectors.isVisible())) {
|
|
41
|
+
await moreActionsSelectors.click();
|
|
42
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 1000) + 500));
|
|
43
|
+
}
|
|
44
|
+
let removeConnectionButton = await getButtonByText(page, 'Remove Connection', inMain);
|
|
45
|
+
if (!removeConnectionButton) {
|
|
46
|
+
removeConnectionButton = await getButtonByText(page, 'Remove', inMain);
|
|
47
|
+
}
|
|
48
|
+
if (!removeConnectionButton) {
|
|
49
|
+
removeConnectionButton = await getButtonByText(page, 'Connected', inMain);
|
|
50
|
+
}
|
|
51
|
+
if (removeConnectionButton) {
|
|
52
|
+
return { status: 'already_connected', message: `Already connected with ${username}.` };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const connectResultWithNote = await handleConnectButtonClick(page, username, inMain, true);
|
|
56
|
+
if (connectResultWithNote.status === 'success') {
|
|
57
|
+
return connectResultWithNote;
|
|
58
|
+
}
|
|
59
|
+
let sentConnectionButton = await getButtonByText(page, 'Sent connection', inMain);
|
|
60
|
+
if (!sentConnectionButton) {
|
|
61
|
+
sentConnectionButton = await getButtonByText(page, 'Pending', inMain);
|
|
62
|
+
}
|
|
63
|
+
if (!sentConnectionButton) {
|
|
64
|
+
sentConnectionButton = await getButtonByText(page, 'Invitation sent', inMain);
|
|
65
|
+
}
|
|
66
|
+
if (sentConnectionButton) {
|
|
67
|
+
return { status: 'pending', message: `Connection request already pending for ${username}.` };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
status: 'not_found',
|
|
72
|
+
message: `Could not find Connect button for ${username}. Please analyze dom and decide what to do next.`
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function handleConnectButtonClick(page, username, inMain = true, handleSendWithoutNote = true) {
|
|
77
|
+
let connectButton = await getButtonByText(page, 'Connect', inMain);
|
|
78
|
+
if (!connectButton || !(await connectButton.isVisible())) {
|
|
79
|
+
connectButton = await getButtonByText(page, 'Connect', inMain);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (connectButton && (await connectButton.isVisible())) {
|
|
83
|
+
try {
|
|
84
|
+
await connectButton.click();
|
|
85
|
+
|
|
86
|
+
if (handleSendWithoutNote) {
|
|
87
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
88
|
+
|
|
89
|
+
let sendWithoutNoteButton = await getButtonByText(page, 'Send without a note', false);
|
|
90
|
+
if (!sendWithoutNoteButton) {
|
|
91
|
+
sendWithoutNoteButton = await getButtonByText(page, 'without a note', false);
|
|
92
|
+
}
|
|
93
|
+
if (!sendWithoutNoteButton) {
|
|
94
|
+
sendWithoutNoteButton = await getButtonByText(page, 'Send', false);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (sendWithoutNoteButton && (await sendWithoutNoteButton.isVisible())) {
|
|
98
|
+
try {
|
|
99
|
+
await sendWithoutNoteButton.click();
|
|
100
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
101
|
+
return { status: 'success', message: `Connection request sent successfully to ${username}` };
|
|
102
|
+
} catch (error) {
|
|
103
|
+
return { status: 'unclear', message: `Error clicking Send without a note button for ${username}` };
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
const existingModal = await page.locator('[data-test-modal-id="send-invite-modal"]').isVisible();
|
|
107
|
+
if (existingModal) {
|
|
108
|
+
return { status: 'success', message: `Modal already open for ${username}` };
|
|
109
|
+
} else {
|
|
110
|
+
return { status: 'unclear', message: `No send without a note button found for ${username}` };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
115
|
+
return { status: 'success', message: `Connection request sent successfully to ${username}` };
|
|
116
|
+
}
|
|
117
|
+
} catch (error) {
|
|
118
|
+
return { status: 'error', message: `Error clicking Connect button for ${username}: ${error.message}` };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { status: 'not_found', message: `Connect button not found for ${username}` };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function getButtonByText(page, text, inMain = false) {
|
|
126
|
+
let buttons = [];
|
|
127
|
+
|
|
128
|
+
if (inMain) {
|
|
129
|
+
const mainContent = page.locator('main');
|
|
130
|
+
buttons = await mainContent.locator('button').all();
|
|
131
|
+
const spans = await page.locator('div[role="button"] span').all();
|
|
132
|
+
buttons = [...buttons, ...spans];
|
|
133
|
+
} else {
|
|
134
|
+
buttons = await page.locator('button').all();
|
|
135
|
+
const spans = await page.locator('div[role="button"] span').all();
|
|
136
|
+
buttons = [...buttons, ...spans];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (let i = 0; i < buttons.length; i++) {
|
|
140
|
+
const button = buttons[i];
|
|
141
|
+
try {
|
|
142
|
+
if (!(await button.isVisible())) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const buttonText = await button.textContent() || await button.innerText() || '';
|
|
146
|
+
if (buttonText.trim() === text) {
|
|
147
|
+
return button;
|
|
148
|
+
}
|
|
149
|
+
} catch (error) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
export async function executeSendMessages(page, action) {
|
|
2
|
+
const username = action.parameters?.username || action.username;
|
|
3
|
+
let messages = action.parameters?.messages || action.messages;
|
|
4
|
+
|
|
5
|
+
if (!username || !messages) {
|
|
6
|
+
throw new Error('Missing username or messages for send_messages');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (typeof messages === 'string') {
|
|
10
|
+
messages = [messages];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const profileUrl = `https://www.linkedin.com/in/${username}`;
|
|
14
|
+
await page.goto(profileUrl, {
|
|
15
|
+
waitUntil: 'load',
|
|
16
|
+
timeout: Math.floor(Math.random() * 20000) + 40000
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
await page.waitForLoadState('domcontentloaded');
|
|
20
|
+
await page.evaluate(() => { window.scrollTo(0, 300); });
|
|
21
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 2000) + 500));
|
|
22
|
+
|
|
23
|
+
await closeAllConversationBubbles(page);
|
|
24
|
+
|
|
25
|
+
const messageBtn = await getButtonByText(page, 'Message', true);
|
|
26
|
+
if (!messageBtn) {
|
|
27
|
+
return {
|
|
28
|
+
action: 'send_messages',
|
|
29
|
+
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.` },
|
|
30
|
+
status: 'failed'
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
await messageBtn.click();
|
|
36
|
+
} catch (error) {
|
|
37
|
+
return {
|
|
38
|
+
action: 'send_messages',
|
|
39
|
+
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.` },
|
|
40
|
+
status: 'failed'
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await findAndClickMessageInput(page);
|
|
45
|
+
|
|
46
|
+
for (const message of messages) {
|
|
47
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 100) + 500));
|
|
48
|
+
await new Promise(resolve => setTimeout(resolve, 200));
|
|
49
|
+
|
|
50
|
+
await typeLikeHuman(page, message);
|
|
51
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 500) + 500));
|
|
52
|
+
|
|
53
|
+
const clickedSend = await clickSendButton(page);
|
|
54
|
+
if (!clickedSend) {
|
|
55
|
+
return {
|
|
56
|
+
action: 'send_messages',
|
|
57
|
+
result: { status: 'send_button_not_found', message: 'Could not find send button.' },
|
|
58
|
+
status: 'failed'
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 1000) + 500));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await closeAllConversationBubbles(page);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
action: 'send_messages',
|
|
69
|
+
result: { status: 'success', message: `Message successfully sent to ${username}` },
|
|
70
|
+
status: 'success'
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function typeLikeHuman(page, text) {
|
|
75
|
+
for (const char of text) {
|
|
76
|
+
await page.keyboard.type(char);
|
|
77
|
+
await new Promise(resolve => setTimeout(resolve, Math.floor(Math.random() * 100) + 50));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function findAndClickMessageInput(page) {
|
|
82
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
83
|
+
|
|
84
|
+
const editorClicked = await page.evaluate(() => {
|
|
85
|
+
const editor = document.querySelector('.msg-form__contenteditable[contenteditable="true"]');
|
|
86
|
+
if (editor) {
|
|
87
|
+
editor.focus();
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (editorClicked) {
|
|
94
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const placeholderClicked = await page.evaluate(() => {
|
|
99
|
+
const placeholder = document.querySelector('.msg-form__placeholder');
|
|
100
|
+
if (placeholder) {
|
|
101
|
+
placeholder.click();
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (placeholderClicked) {
|
|
108
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function closeAllConversationBubbles(page) {
|
|
113
|
+
await page.evaluate(() => {
|
|
114
|
+
const closeButtons = document.querySelectorAll('.msg-overlay-bubble-header__control--new-convo-btn');
|
|
115
|
+
closeButtons.forEach(btn => {
|
|
116
|
+
try {
|
|
117
|
+
btn.click();
|
|
118
|
+
} catch (e) {
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function clickSendButton(page) {
|
|
126
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
127
|
+
|
|
128
|
+
const clicked = await page.evaluate(() => {
|
|
129
|
+
const sendButton = document.querySelector('.msg-form__send-button:not([disabled])');
|
|
130
|
+
if (sendButton) {
|
|
131
|
+
sendButton.click();
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return clicked;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function getButtonByText(page, text, inMain = false) {
|
|
141
|
+
let buttons = [];
|
|
142
|
+
if (inMain) {
|
|
143
|
+
buttons = await page.locator('main button').all();
|
|
144
|
+
} else {
|
|
145
|
+
buttons = await page.locator('button').all();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
for (const button of buttons) {
|
|
149
|
+
try {
|
|
150
|
+
const buttonText = await button.textContent();
|
|
151
|
+
if (buttonText && buttonText.includes(text)) {
|
|
152
|
+
const isVisible = await button.isVisible();
|
|
153
|
+
if (isVisible) {
|
|
154
|
+
return button;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} catch (error) {
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return null;
|
|
162
|
+
}
|