mirror of
https://github.com/nodemailer/wildduck.git
synced 2024-11-11 01:54:04 +08:00
177 lines
7.7 KiB
JavaScript
177 lines
7.7 KiB
JavaScript
'use strict';
|
|
|
|
const MailComposer = require('nodemailer/lib/mail-composer');
|
|
const MessageSplitter = require('./message-splitter');
|
|
const db = require('./db');
|
|
const consts = require('./consts');
|
|
const errors = require('./errors');
|
|
const maildrop = require('./maildrop');
|
|
|
|
module.exports = (options, callback) => {
|
|
if (!options.sender || /mailer-daemon|no-?reply/gi.test(options.sender)) {
|
|
return callback(null, false);
|
|
}
|
|
|
|
let curtime = new Date();
|
|
db.database.collection('autoreplies').findOne(
|
|
{
|
|
user: options.userData._id,
|
|
start: {
|
|
$lte: curtime
|
|
},
|
|
end: {
|
|
$gte: curtime
|
|
}
|
|
},
|
|
(err, autoreply) => {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
|
|
if (!autoreply || !autoreply.status) {
|
|
return callback(null, false);
|
|
}
|
|
|
|
// 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)
|
|
|
|
let messageHeaders = false;
|
|
let messageSplitter = new MessageSplitter();
|
|
|
|
messageSplitter.once('headers', headers => {
|
|
messageHeaders = headers;
|
|
|
|
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);
|
|
}
|
|
|
|
db.redis
|
|
.multi()
|
|
// delete all old entries
|
|
.zremrangebyscore('war:' + autoreply._id, '-inf', Date.now() - consts.MAX_AUTOREPLY_INTERVAL)
|
|
// add new entry if not present
|
|
.zadd('war:' + autoreply._id, 'NX', Date.now(), options.sender)
|
|
// if no-one touches this key from now, then delete after max interval has passed
|
|
.expire('war:' + autoreply._id, consts.MAX_AUTOREPLY_INTERVAL)
|
|
.exec((err, result) => {
|
|
if (err) {
|
|
errors.notify(err, { userId: options.userData._id });
|
|
return callback(null, false);
|
|
}
|
|
|
|
if (!result || !result[1] || !result[1][1]) {
|
|
// already responded
|
|
return callback(null, false);
|
|
}
|
|
|
|
// check limiting counters
|
|
options.messageHandler.counters.ttlcounter('wda:' + options.userData._id, 1, consts.MAX_AUTOREPLIES, false, (err, result) => {
|
|
if (err || !result.success) {
|
|
return callback(null, false);
|
|
}
|
|
|
|
let data = {
|
|
envelope: {
|
|
from: '',
|
|
to: options.sender
|
|
},
|
|
from: {
|
|
name: options.userData.name,
|
|
address: options.recipient
|
|
},
|
|
to: options.sender,
|
|
subject: autoreply.subject
|
|
? 'Auto: ' + autoreply.subject
|
|
: {
|
|
prepared: true,
|
|
value: 'Auto: Re: ' + headers.getFirst('Subject')
|
|
},
|
|
headers: {
|
|
'Auto-Submitted': 'auto-replied',
|
|
'X-WD-Autoreply-For': (options.parentId || '').toString()
|
|
},
|
|
inReplyTo: headers.getFirst('Message-ID'),
|
|
references: (headers.getFirst('Message-ID') + ' ' + headers.getFirst('References')).trim(),
|
|
text: autoreply.text,
|
|
html: autoreply.html
|
|
};
|
|
|
|
let compiler = new MailComposer(data);
|
|
let message = maildrop(
|
|
{
|
|
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);
|
|
}
|
|
db.database.collection('messagelog').insertOne(
|
|
{
|
|
id: args[0].id,
|
|
messageId: args[0].messageId,
|
|
parentId: options.parentId,
|
|
action: 'AUTOREPLY',
|
|
from: '',
|
|
to: options.sender,
|
|
created: new Date()
|
|
},
|
|
() => callback(err, args && args[0].id)
|
|
);
|
|
}
|
|
);
|
|
|
|
if (message) {
|
|
compiler
|
|
.compile()
|
|
.createReadStream()
|
|
.pipe(message);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
messageSplitter.on('error', () => false);
|
|
messageSplitter.on('data', () => false);
|
|
messageSplitter.on('end', () => false);
|
|
|
|
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 {
|
|
setImmediate(writeNextChunk);
|
|
}
|
|
};
|
|
setImmediate(writeNextChunk);
|
|
});
|
|
}
|
|
);
|
|
};
|