Use juice to prepare html messages
This commit is contained in:
parent
c031ea7747
commit
4f2d66c30c
8 changed files with 251 additions and 67 deletions
|
@ -5,6 +5,7 @@ let log = require('npmlog');
|
|||
let nodemailer = require('nodemailer');
|
||||
let openpgpEncrypt = require('nodemailer-openpgp').openpgpEncrypt;
|
||||
let settings = require('./models/settings');
|
||||
let tools = require('./tools');
|
||||
let Handlebars = require('handlebars');
|
||||
let fs = require('fs');
|
||||
let path = require('path');
|
||||
|
@ -48,20 +49,30 @@ module.exports.sendMail = (mail, template, callback) => {
|
|||
mail.html = htmlRenderer(template.data || {});
|
||||
}
|
||||
|
||||
getTemplate(template.text, (err, textRenderer) => {
|
||||
tools.prepareHtml(mail.html, (err, prepareHtml) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
// ignore
|
||||
}
|
||||
|
||||
if (textRenderer) {
|
||||
mail.text = textRenderer(template.data || {});
|
||||
} else if (mail.html) {
|
||||
mail.text = htmlToText.fromString(mail.html, {
|
||||
wordwrap: 130
|
||||
});
|
||||
if (prepareHtml) {
|
||||
mail.html = prepareHtml;
|
||||
}
|
||||
|
||||
module.exports.transport.sendMail(mail, callback);
|
||||
getTemplate(template.text, (err, textRenderer) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (textRenderer) {
|
||||
mail.text = textRenderer(template.data || {});
|
||||
} else if (mail.html) {
|
||||
mail.text = htmlToText.fromString(mail.html, {
|
||||
wordwrap: 130
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.transport.sendMail(mail, callback);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -275,31 +275,43 @@ module.exports.create = (campaign, opts, callback) => {
|
|||
keys.push('cid');
|
||||
values.push(cid);
|
||||
|
||||
db.getConnection((err, connection) => {
|
||||
tools.prepareHtml(campaign.html, (err, preparedHtml) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
log.error('jsdom', err);
|
||||
preparedHtml = campaign.html;
|
||||
}
|
||||
|
||||
let query = 'INSERT INTO campaigns (`' + keys.join('`, `') + '`) VALUES (' + values.map(() => '?').join(',') + ')';
|
||||
connection.query(query, values, (err, result) => {
|
||||
connection.release();
|
||||
if (preparedHtml) {
|
||||
keys.push('html_prepared');
|
||||
values.push(preparedHtml);
|
||||
}
|
||||
|
||||
db.getConnection((err, connection) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
let campaignId = result && result.insertId || false;
|
||||
if (!campaignId) {
|
||||
return next(null, false);
|
||||
}
|
||||
|
||||
// we are going to aqcuire a lot of log info, so we are putting
|
||||
// sending logs into separate tables
|
||||
createCampaignTables(campaignId, err => {
|
||||
let query = 'INSERT INTO campaigns (`' + keys.join('`, `') + '`) VALUES (' + values.map(() => '?').join(',') + ')';
|
||||
connection.query(query, values, (err, result) => {
|
||||
connection.release();
|
||||
if (err) {
|
||||
// FIXME: rollback
|
||||
return next(err);
|
||||
}
|
||||
return next(null, campaignId);
|
||||
|
||||
let campaignId = result && result.insertId || false;
|
||||
if (!campaignId) {
|
||||
return next(null, false);
|
||||
}
|
||||
|
||||
// we are going to aqcuire a lot of log info, so we are putting
|
||||
// sending logs into separate tables
|
||||
createCampaignTables(campaignId, err => {
|
||||
if (err) {
|
||||
// FIXME: rollback
|
||||
return next(err);
|
||||
}
|
||||
return next(null, campaignId);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -355,8 +367,8 @@ module.exports.create = (campaign, opts, callback) => {
|
|||
return callback(new Error('Selected template not found'));
|
||||
}
|
||||
|
||||
keys = keys.concat(['html', 'text']);
|
||||
values = values.concat([template.html, template.text]);
|
||||
campaign.html = template.html;
|
||||
campaign.text = template.text;
|
||||
|
||||
create(callback);
|
||||
});
|
||||
|
@ -410,58 +422,70 @@ module.exports.update = (id, updates, callback) => {
|
|||
}
|
||||
});
|
||||
|
||||
db.getConnection((err, connection) => {
|
||||
tools.prepareHtml(campaign.html, (err, preparedHtml) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
log.error('jsdom', err);
|
||||
preparedHtml = campaign.html;
|
||||
}
|
||||
|
||||
values.push(id);
|
||||
if (preparedHtml) {
|
||||
keys.push('html_prepared');
|
||||
values.push(preparedHtml);
|
||||
}
|
||||
|
||||
connection.query('UPDATE campaigns SET ' + keys.map(key => '`' + key + '`=?').join(', ') + ' WHERE id=? LIMIT 1', values, (err, result) => {
|
||||
db.getConnection((err, connection) => {
|
||||
if (err) {
|
||||
connection.release();
|
||||
return callback(err);
|
||||
}
|
||||
let affected = result && result.affectedRows || false;
|
||||
|
||||
if (!affected) {
|
||||
connection.release();
|
||||
return callback(null, affected);
|
||||
}
|
||||
values.push(id);
|
||||
|
||||
connection.query('SELECT `type`, `source_url` FROM campaigns WHERE id=? LIMIT 1', [id], (err, rows) => {
|
||||
connection.query('UPDATE campaigns SET ' + keys.map(key => '`' + key + '`=?').join(', ') + ' WHERE id=? LIMIT 1', values, (err, result) => {
|
||||
if (err) {
|
||||
connection.release();
|
||||
return callback(err);
|
||||
}
|
||||
let affected = result && result.affectedRows || false;
|
||||
|
||||
if (!rows || !rows[0] || rows[0].type !== 2) {
|
||||
// if not RSS, then nothing to do here
|
||||
if (!affected) {
|
||||
connection.release();
|
||||
return callback(null, affected);
|
||||
}
|
||||
|
||||
// update seen rss entries to avoid sending old entries to subscribers
|
||||
feed.fetch(rows[0].source_url, (err, entries) => {
|
||||
connection.query('SELECT `type`, `source_url` FROM campaigns WHERE id=? LIMIT 1', [id], (err, rows) => {
|
||||
if (err) {
|
||||
connection.release();
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
let query = 'INSERT IGNORE INTO `rss` (`parent`,`guid`,`pubdate`) VALUES ' + entries.map(() => '(?,?,?)').join(',');
|
||||
|
||||
values = [];
|
||||
entries.forEach(entry => {
|
||||
values.push(id, entry.guid, entry.date);
|
||||
});
|
||||
|
||||
connection.query(query, values, err => {
|
||||
if (!rows || !rows[0] || rows[0].type !== 2) {
|
||||
// if not RSS, then nothing to do here
|
||||
connection.release();
|
||||
if (err) {
|
||||
// too late to report as failed
|
||||
log.error('RSS', err);
|
||||
}
|
||||
return callback(null, affected);
|
||||
}
|
||||
|
||||
// update seen rss entries to avoid sending old entries to subscribers
|
||||
feed.fetch(rows[0].source_url, (err, entries) => {
|
||||
if (err) {
|
||||
connection.release();
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
let query = 'INSERT IGNORE INTO `rss` (`parent`,`guid`,`pubdate`) VALUES ' + entries.map(() => '(?,?,?)').join(',');
|
||||
|
||||
values = [];
|
||||
entries.forEach(entry => {
|
||||
values.push(id, entry.guid, entry.date);
|
||||
});
|
||||
|
||||
connection.query(query, values, err => {
|
||||
connection.release();
|
||||
if (err) {
|
||||
// too late to report as failed
|
||||
log.error('RSS', err);
|
||||
}
|
||||
return callback(null, affected);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
38
lib/tools.js
38
lib/tools.js
|
@ -4,6 +4,8 @@ let db = require('./db');
|
|||
let slugify = require('slugify');
|
||||
let Isemail = require('isemail');
|
||||
let urllib = require('url');
|
||||
let juice = require('juice');
|
||||
let jsdom = require('jsdom');
|
||||
|
||||
let blockedUsers = ['abuse', 'admin', 'billing', 'compliance', 'devnull', 'dns', 'ftp', 'hostmaster', 'inoc', 'ispfeedback', 'ispsupport', 'listrequest', 'list', 'maildaemon', 'noc', 'noreply', 'noreply', 'null', 'phish', 'phishing', 'postmaster', 'privacy', 'registrar', 'root', 'security', 'spam', 'support', 'sysadmin', 'tech', 'undisclosedrecipients', 'unsubscribe', 'usenet', 'uucp', 'webmaster', 'www'];
|
||||
|
||||
|
@ -16,7 +18,8 @@ module.exports = {
|
|||
updateMenu,
|
||||
validateEmail,
|
||||
formatMessage,
|
||||
getMessageLinks
|
||||
getMessageLinks,
|
||||
prepareHtml
|
||||
};
|
||||
|
||||
function toDbKey(key) {
|
||||
|
@ -178,3 +181,36 @@ function formatMessage(serviceUrl, campaign, list, subscription, message, filter
|
|||
return value ? filter(value) : match;
|
||||
});
|
||||
}
|
||||
|
||||
function prepareHtml(html, callback) {
|
||||
if (!(html || '').toString().trim()) {
|
||||
return callback(null, false);
|
||||
}
|
||||
|
||||
jsdom.env(html, (err, win) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
let head = win.document.querySelector('head');
|
||||
let hasCharsetTag = false;
|
||||
let metaTags = win.document.querySelectorAll('meta');
|
||||
if (metaTags) {
|
||||
for (let i = 0; i < metaTags.length; i++) {
|
||||
if (metaTags[i].hasAttribute('charset')) {
|
||||
metaTags[i].setAttribute('charset', 'utf-8');
|
||||
hasCharsetTag = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasCharsetTag) {
|
||||
let charsetTag = win.document.createElement('meta');
|
||||
charsetTag.setAttribute('charset', 'utf-8');
|
||||
head.appendChild(charsetTag);
|
||||
}
|
||||
let preparedHtml = '<!doctype html><html>' + win.document.documentElement.innerHTML + '</html>';
|
||||
|
||||
return callback(null, juice(preparedHtml));
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue