codecombat/scripts/addZenProspectLeadsToClose.js

195 lines
6.3 KiB
JavaScript
Raw Normal View History

// Copy ZenProspect contacts with email replies into Close.io leads
'use strict';
if (process.argv.length !== 4) {
console.log("Usage: node <script> <Close.io general API key> <ZenProspect auth token>");
process.exit();
}
const closeIoApiKey = process.argv[2];
const zpAuthToken = process.argv[3];
const scriptStartTime = new Date();
const async = require('async');
const request = require('request');
const zpPageSize = 100;
getZPRepliedContacts((err, emailContactMap) => {
if (err) {
console.log(err);
return;
}
const tasks = [];
for (const email in emailContactMap) {
const contact = emailContactMap[email];
// if (contact.organization !== 'Cabarrus County Schools') continue;
tasks.push(createUpsertCloseLeadFn(contact));
}
async.parallel(tasks, (err, results) => {
if (err) console.log(err);
log("Script runtime: " + (new Date() - scriptStartTime));
});
});
function createCloseLead(zpContact, done) {
const postData = {
name: zpContact.organization,
status: 'Contacted',
contacts: [
{
name: zpContact.name,
title: zpContact.title,
emails: [{email: zpContact.email}]
}
],
custom: {
lastUpdated: new Date(),
'Lead Origin': 'outbound campaign'
}
};
if (zpContact.phone) {
postData.contacts[0].phones = [{phone: zpContact.phone}];
}
const options = {
uri: `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/`,
body: JSON.stringify(postData)
};
request.post(options, (error, response, body) => {
if (error) return done(error);
const newLead = JSON.parse(body);
if (newLead.errors || newLead['field-errors']) {
console.error(`New lead POST error for ${zpContact.name} ${zpContact.organization}`);
return done(newLead.errors || newLead['field-errors']);
}
return done();
});
}
function updateCloseLead(zpContact, existingLead, done) {
const putData = {
status: 'Contacted',
'custom.lastUpdated': new Date(),
'custom.Lead Origin': 'outbound campaign'
};
const options = {
uri: `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/${existingLead.id}/`,
body: JSON.stringify(putData)
};
request.put(options, (error, response, body) => {
if (error) return done(error);
const result = JSON.parse(body);
if (result.errors || result['field-errors']) {
return done(`Update existing lead PUT error for ${existingLead.id} ${zpContact.email} ${result.errors || result['field-errors']}`);
}
const postData = {
lead_id: existingLead.id,
name: zpContact.name,
title: zpContact.title,
emails: [{email: zpContact.email}]
};
const options = {
uri: `https://${closeIoApiKey}:X@app.close.io/api/v1/contact/`,
body: JSON.stringify(postData)
};
request.post(options, (error, response, body) => {
if (error) return done(error);
const result = JSON.parse(body);
if (result.errors || result['field-errors']) {
return done(`New Contact POST error for ${existingLead.id} ${zpContact.email} ${result.errors || result['field-errors']}`);
}
return done();
});
});
}
function createUpsertCloseLeadFn(zpContact) {
return (done) => {
// console.log(`DEBUG: createUpsertCloseLeadFn ${zpContact.organization} ${zpContact.email}`);
const query = `email:${zpContact.email}`;
const url = `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/?query=${encodeURIComponent(query)}`;
request.get(url, (error, response, body) => {
if (error) return done(error);
const data = JSON.parse(body);
if (data.total_results != 0) return done();
const query = `name:${zpContact.organization}`;
const url = `https://${closeIoApiKey}:X@app.close.io/api/v1/lead/?query=${encodeURIComponent(query)}`;
request.get(url, (error, response, body) => {
if (error) return done(error);
const data = JSON.parse(body);
if (data.total_results === 0) {
console.log(`DEBUG: Creating lead for ${zpContact.organization} ${zpContact.email}`);
return createCloseLead(zpContact, done);
}
else {
const existingLead = data.data[0];
console.log(`DEBUG: Adding ${zpContact.organization} ${zpContact.email} to ${existingLead.id}`);
return updateCloseLead(zpContact, existingLead, done);
}
});
});
};
}
function getZPRepliedContactsPage(contacts, page, done) {
// console.log(`DEBUG: Fetching page ${page} ${zpPageSize}...`);
const options = {
url: `https://www.zenprospect.com/api/v1/contacts/search?codecombat_special_auth_token=${zpAuthToken}&page=${page}&per_page=${zpPageSize}`,
headers: {
'Accept': 'application/json'
}
};
request.get(options, (err, response, body) => {
if (err) return done(err);
const data = JSON.parse(body);
for (let contact of data.contacts) {
if (contact.email_replied) {
contacts.push({
organization: contact.organization_name,
name: contact.name,
title: contact.title,
email: contact.email,
phone: contact.phone,
data: contact
});
}
}
return done(null, data.pipeline_total);
});
}
function getZPRepliedContacts(done) {
// Get first page to get total contact count for parallized page fetches
const contacts = [];
getZPRepliedContactsPage(contacts, 0, (err, total) => {
if (err) return done(err);
const createGetZPLeadsPage = (leads, page) => {
return (done) => {
getZPRepliedContactsPage(leads, page, done);
};
}
const tasks = [];
for (let i = 1; (i - 1) * zpPageSize < total; i++) {
tasks.push(createGetZPLeadsPage(contacts, i));
}
async.parallel(tasks, (err, results) => {
if (err) return done(err);
const emailContactMap = {};
for (const contact of contacts) {
if (!contact.organization || !contact.name || !contact.title || !contact.email) {
console.log(JSON.stringify(contact, null, 2));
return done(`DEBUG: missing data for zp contact:`);
}
if (!emailContactMap[contact.email]) emailContactMap[contact.email] = contact;
}
log(`${total} total ZP contacts, ${Object.keys(emailContactMap).length} with replies`);
return done(null, emailContactMap);
});
});
}
function log(str) {
console.log(new Date().toISOString() + " " + str);
}