wildduck/lib/autoreply.js

160 lines
6.5 KiB
JavaScript
Raw Normal View History

2017-05-08 00:00:04 +08:00
'use strict';
const MailComposer = require('nodemailer/lib/mail-composer');
const MessageSplitter = require('./message-splitter');
2017-07-30 23:07:35 +08:00
const consts = require('./consts');
2017-10-03 18:09:16 +08:00
const errors = require('./errors');
2017-05-08 00:00:04 +08:00
2018-01-04 18:03:25 +08:00
module.exports = (options, autoreplyData, callback) => {
2017-07-30 23:07:35 +08:00
if (!options.sender || /mailer-daemon|no-?reply/gi.test(options.sender)) {
2017-05-08 00:00:04 +08:00
return callback(null, false);
}
2018-01-04 18:03:25 +08:00
// step 1. check if recipient is valid (non special address)
// step 2. check if recipient not in cache list
// step 3. parse headers, check if not automatic message
// step 4. prepare message with special headers (in-reply-to, references, Auto-Submitted)
2017-05-08 00:00:04 +08:00
2018-01-04 18:03:25 +08:00
let messageHeaders = false;
let messageSplitter = new MessageSplitter();
2017-07-30 23:07:35 +08:00
2018-01-04 18:03:25 +08:00
messageSplitter.once('headers', headers => {
messageHeaders = headers;
2017-07-30 23:07:35 +08:00
2018-01-04 18:03:25 +08:00
let autoSubmitted = headers.getFirst('Auto-Submitted');
if (autoSubmitted && autoSubmitted.toLowerCase() !== 'no') {
// skip automatic messages
return callback(null, false);
}
let precedence = headers.getFirst('Precedence');
if (precedence && ['list', 'junk', 'bulk'].includes(precedence.toLowerCase())) {
return callback(null, false);
}
let listUnsubscribe = headers.getFirst('List-Unsubscribe');
if (listUnsubscribe) {
return callback(null, false);
}
let suppressAutoresponse = headers.getFirst('X-Auto-Response-Suppress');
if (suppressAutoresponse && /OOF|AutoReply/i.test(suppressAutoresponse)) {
return callback(null, false);
}
2017-06-03 14:51:58 +08:00
2018-01-04 18:03:25 +08:00
options.db.redis
.multi()
// delete all old entries
.zremrangebyscore('war:' + autoreplyData._id, '-inf', Date.now() - consts.MAX_AUTOREPLY_INTERVAL)
// add new entry if not present
.zadd('war:' + autoreplyData._id, 'NX', Date.now(), options.sender)
// if no-one touches this key from now, then delete after max interval has passed
.expire('war:' + autoreplyData._id, consts.MAX_AUTOREPLY_INTERVAL)
.exec((err, result) => {
if (err) {
errors.notify(err, { userId: autoreplyData._id });
2017-12-15 17:02:47 +08:00
return callback(null, false);
}
2018-01-04 18:03:25 +08:00
if (!result || !result[1] || !result[1][1]) {
// already responded
2017-12-15 17:02:47 +08:00
return callback(null, false);
}
2017-05-08 00:00:04 +08:00
2018-01-04 18:03:25 +08:00
// check limiting counters
options.messageHandler.counters.ttlcounter('wda:' + autoreplyData._id, 1, consts.MAX_AUTOREPLIES, false, (err, result) => {
if (err || !result.success) {
return callback(null, false);
}
2017-07-30 23:07:35 +08:00
2018-01-24 22:47:09 +08:00
let inReplyTo = Buffer.from(headers.getFirst('Message-ID'), 'binary').toString();
2018-01-04 18:03:25 +08:00
let data = {
envelope: {
from: '',
to: options.sender
},
from: {
2018-01-24 17:29:12 +08:00
name: autoreplyData.name || (options.userData && options.userData.name),
2018-01-04 18:03:25 +08:00
address: options.recipient
},
to: options.sender,
subject: (autoreplyData.subject && 'Auto: ' + autoreplyData.subject) || {
prepared: true,
2018-01-24 22:47:09 +08:00
value: 'Auto: Re: ' + Buffer.from(headers.getFirst('Subject'), 'binary').toString()
2018-01-04 18:03:25 +08:00
},
headers: {
'Auto-Submitted': 'auto-replied',
2018-01-09 19:50:29 +08:00
'X-WD-Autoreply-For': (options.parentId || options.queueId).toString()
2018-01-04 18:03:25 +08:00
},
2018-01-24 22:47:09 +08:00
inReplyTo,
references: (inReplyTo + ' ' + Buffer.from(headers.getFirst('References'), 'binary').toString()).trim(),
2018-01-04 18:03:25 +08:00
text: autoreplyData.text,
html: autoreplyData.html
};
let compiler = new MailComposer(data);
let message = options.maildrop.push(
{
parentId: options.parentId,
reason: 'autoreply',
from: '',
to: options.sender,
interface: 'autoreplies'
},
(err, ...args) => {
if (err || !args[0]) {
if (err) {
err.code = err.code || 'ERRCOMPOSE';
}
return callback(err, ...args);
2017-12-15 17:02:47 +08:00
}
2018-01-09 19:50:29 +08:00
let logentry = {
id: args[0].id,
messageId: args[0].messageId,
action: 'AUTOREPLY',
from: '',
to: options.sender,
sender: options.recipient,
created: new Date()
};
if (options.parentId) {
logentry.parentId = options.parentId;
}
if (options.queueId) {
logentry.queueId = options.queueId;
}
2018-10-19 17:19:43 +08:00
return callback(err, args && args[0].id);
2018-01-04 18:03:25 +08:00
}
);
2017-07-30 23:07:35 +08:00
2018-01-04 18:03:25 +08:00
if (message) {
compiler
.compile()
.createReadStream()
.pipe(message);
}
});
2017-12-15 17:02:47 +08:00
});
2018-01-04 18:03:25 +08:00
});
2017-12-15 17:02:47 +08:00
2018-01-04 18:03:25 +08:00
messageSplitter.on('error', () => false);
messageSplitter.on('data', () => false);
messageSplitter.on('end', () => false);
2017-12-15 17:02:47 +08:00
2018-01-04 18:03:25 +08:00
setImmediate(() => {
let pos = 0;
let writeNextChunk = () => {
if (messageHeaders || pos >= options.chunks.length) {
return messageSplitter.end();
}
let chunk = options.chunks[pos++];
if (!messageSplitter.write(chunk)) {
return messageSplitter.once('drain', writeNextChunk);
} else {
2017-12-15 17:02:47 +08:00
setImmediate(writeNextChunk);
2018-01-04 18:03:25 +08:00
}
};
setImmediate(writeNextChunk);
});
2017-05-08 00:00:04 +08:00
};