import database from './database-pg.js';
import LinkedInAutomation from './linkedin.js';
import dotenv from 'dotenv';
dotenv.config();
class FollowUpWorker {
constructor() {
this.linkedinBot = null;
this.isRunning = false;
this.checkInterval = 60 * 60 * 1000; // Check every hour
}
async initialize(cdpUrl) {
await database.init();
this.linkedinBot = new LinkedInAutomation();
const result = await this.linkedinBot.connect(cdpUrl);
if (!result.success) {
throw new Error(`Failed to connect to browser: ${result.error}`);
}
console.log('Follow-up worker initialized');
}
async setupSession(userId) {
const session = await database.getSession(userId);
if (!session || !session.is_valid) {
throw new Error('No valid session found for user');
}
const result = await this.linkedinBot.setupSession(session.cookies[0].value);
if (!result.success) {
await database.invalidateSession(userId);
throw new Error('Session authentication failed');
}
return true;
}
async processFollowUps() {
try {
console.log('Checking for pending follow-ups...');
const pending = await database.getPendingFollowups();
console.log(`Found ${pending.length} pending follow-ups`);
for (const item of pending) {
try {
const lead = await database.getLead(item.sequence.profile_url);
if (!lead) {
console.error(`Lead not found for ${item.sequence.profile_url}`);
continue;
}
// Setup session for this user
await this.setupSession(item.sequence.user_id);
// Check if lead has responded
const responseCheck = await this.linkedinBot.checkForResponse(item.sequence.profile_url);
if (responseCheck.success && responseCheck.data.hasUnread) {
console.log(`Lead ${lead.name} has responded - skipping follow-up`);
// Mark sequence as completed
await database.updateSequence(item.sequence.id, { is_active: false, completion_reason: 'responded' });
// Update message record
const messages = await database.getMessages(lead.id);
const lastMessage = messages[messages.length - 1];
if (lastMessage) {
await database.saveMessage({
...lastMessage,
response_received: true
});
}
continue;
}
// Send follow-up message
console.log(`Sending follow-up stage ${item.stage} to ${lead.name}`);
const result = await this.linkedinBot.sendMessage(
item.sequence.profile_url,
item.message.message,
false
);
if (result.success) {
await database.saveMessage({
lead_id: lead.id,
user_id: item.sequence.user_id,
profile_url: item.sequence.profile_url,
message_text: item.message.message,
sent_at: new Date().toISOString(),
sequence_stage: item.stage,
response_received: false
});
console.log(`Follow-up sent successfully to ${lead.name}`);
} else {
console.error(`Failed to send follow-up to ${lead.name}: ${result.error}`);
}
// Add delay between messages
await this.delay(5000, 10000);
} catch (error) {
console.error(`Error processing follow-up: ${error.message}`);
}
}
console.log('Follow-up processing completed');
} catch (error) {
console.error(`Follow-up worker error: ${error.message}`);
}
}
delay(min, max) {
const ms = Math.floor(Math.random() * (max - min + 1)) + min;
return new Promise(resolve => setTimeout(resolve, ms));
}
start() {
if (this.isRunning) {
console.log('Worker already running');
return;
}
this.isRunning = true;
console.log('Follow-up worker started');
// Process immediately
this.processFollowUps();
// Then schedule recurring checks
this.intervalId = setInterval(() => {
this.processFollowUps();
}, this.checkInterval);
}
stop() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
if (this.linkedinBot) {
this.linkedinBot.disconnect();
}
this.isRunning = false;
console.log('Follow-up worker stopped');
}
}
// Run worker if executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
const worker = new FollowUpWorker();
const cdpUrl = process.env.CDP_URL || 'http://localhost:9222';
worker.initialize(cdpUrl)
.then(() => {
worker.start();
// Handle graceful shutdown
process.on('SIGINT', () => {
console.log('\nShutting down worker...');
worker.stop();
process.exit(0);
});
})
.catch(error => {
console.error('Failed to initialize worker:', error);
process.exit(1);
});
}
export default FollowUpWorker;