2016-06-18 00:05:46 -04:00
|
|
|
// 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;
|
|
|
|
|
2016-07-06 18:48:46 -04:00
|
|
|
getZPContacts((err, emailContactMap) => {
|
2016-06-18 00:05:46 -04:00
|
|
|
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);
|
2016-06-19 02:21:06 -04:00
|
|
|
log("Script runtime: " + (new Date() - scriptStartTime));
|
2016-06-18 00:05:46 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
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) {
|
2016-07-06 18:48:46 -04:00
|
|
|
// console.log(`DEBUG: updateCloseLead ${existingLead.id} ${zpContact.email}`);
|
2016-06-18 00:05:46 -04:00
|
|
|
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}]
|
|
|
|
};
|
2016-07-06 18:48:46 -04:00
|
|
|
if (zpContact.phone) {
|
|
|
|
postData.phones = [{phone: zpContact.phone}];
|
|
|
|
}
|
2016-06-18 00:05:46 -04:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2016-07-06 18:48:46 -04:00
|
|
|
function getZPContactsPage(contacts, searchQuery, done) {
|
2016-06-18 00:05:46 -04:00
|
|
|
const options = {
|
2016-07-06 18:48:46 -04:00
|
|
|
url: `https://www.zenprospect.com/api/v1/contacts/search?${searchQuery}`,
|
2016-06-18 00:05:46 -04:00
|
|
|
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) {
|
2016-07-06 18:48:46 -04:00
|
|
|
contacts.push({
|
|
|
|
organization: contact.organization_name,
|
|
|
|
name: contact.name,
|
|
|
|
title: contact.title,
|
|
|
|
email: contact.email,
|
|
|
|
phone: contact.phone,
|
|
|
|
data: contact
|
|
|
|
});
|
2016-06-18 00:05:46 -04:00
|
|
|
}
|
|
|
|
return done(null, data.pipeline_total);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-07-06 18:48:46 -04:00
|
|
|
function createGetZPAutoResponderContactsPage(contacts, page) {
|
|
|
|
return (done) => {
|
|
|
|
// console.log(`DEBUG: Fetching autoresponder page ${page} ${zpPageSize}...`);
|
|
|
|
let searchQuery = `codecombat_special_auth_token=${zpAuthToken}&page=${page}&per_page=${zpPageSize}&contact_email_autoresponder=true`;
|
|
|
|
getZPContactsPage(contacts, searchQuery, done);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function createGetZPRepliedContactsPage(contacts, page) {
|
|
|
|
return (done) => {
|
|
|
|
// console.log(`DEBUG: Fetching email reply page ${page} ${zpPageSize}...`);
|
|
|
|
let searchQuery = `codecombat_special_auth_token=${zpAuthToken}&page=${page}&per_page=${zpPageSize}&contact_email_replied=true`;
|
|
|
|
getZPContactsPage(contacts, searchQuery, done);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function getZPContacts(done) {
|
|
|
|
// Get first page to get total contact count for future parallized page fetches
|
2016-06-18 00:05:46 -04:00
|
|
|
const contacts = [];
|
2016-07-06 18:48:46 -04:00
|
|
|
createGetZPAutoResponderContactsPage(contacts, 0)((err, autoResponderTotal) => {
|
2016-06-18 00:05:46 -04:00
|
|
|
if (err) return done(err);
|
2016-07-06 18:48:46 -04:00
|
|
|
createGetZPRepliedContactsPage(contacts, 0)((err, repliedTotal) => {
|
2016-06-18 00:05:46 -04:00
|
|
|
if (err) return done(err);
|
2016-07-06 18:48:46 -04:00
|
|
|
|
|
|
|
const tasks = [];
|
|
|
|
for (let i = 1; (i - 1) * zpPageSize < autoResponderTotal; i++) {
|
|
|
|
tasks.push(createGetZPAutoResponderContactsPage(contacts, i));
|
2016-06-18 00:05:46 -04:00
|
|
|
}
|
2016-07-06 18:48:46 -04:00
|
|
|
for (let i = 1; (i - 1) * zpPageSize < repliedTotal; i++) {
|
|
|
|
tasks.push(createGetZPRepliedContactsPage(contacts, i));
|
|
|
|
}
|
|
|
|
|
|
|
|
async.series(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;
|
|
|
|
}
|
|
|
|
// else {
|
|
|
|
// console.log(`DEBUG: already have contact ${contact.email}`);
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
log(`(${autoResponderTotal + repliedTotal}) ${autoResponderTotal} autoresponder ZP contacts ${repliedTotal} ZP contacts ${Object.keys(emailContactMap).length} contacts mapped`);
|
|
|
|
return done(null, emailContactMap);
|
|
|
|
});
|
2016-06-18 00:05:46 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2016-06-19 02:21:06 -04:00
|
|
|
|
|
|
|
function log(str) {
|
|
|
|
console.log(new Date().toISOString() + " " + str);
|
|
|
|
}
|