Add imap-probe
This commit is contained in:
parent
d04dda7fd6
commit
b78a105b79
203
imap-probe.js
Normal file
203
imap-probe.js
Normal file
@ -0,0 +1,203 @@
|
||||
'use strict';
|
||||
|
||||
exports = module.exports = ImapProbe;
|
||||
|
||||
var assert = require('assert'),
|
||||
async = require('async'),
|
||||
debug = require('debug')('imap-probe'),
|
||||
Imap = require('imap'),
|
||||
quotedPrintable = require('quoted-printable');
|
||||
|
||||
// helper function to parse buffer as a multipart
|
||||
function parseMultipart(buffer, boundary) {
|
||||
var parts = buffer.split('\r\n');
|
||||
|
||||
var content = [];
|
||||
var found = false;
|
||||
var headers = false;
|
||||
var consume = false;
|
||||
var encodingQuotedPrintable = false;
|
||||
|
||||
for (var i = 0; i < parts.length; ++i) {
|
||||
if (parts[i].indexOf('--' + boundary) === 0) {
|
||||
// if we get a second boundary but have already found the plain one, stop
|
||||
if (found) break;
|
||||
|
||||
content = [];
|
||||
headers = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// check if we have found the plain/text section
|
||||
if (headers && parts[i].toLowerCase().indexOf('content-type: text/plain') === 0) {
|
||||
found = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (headers && parts[i].toLowerCase().indexOf('content-transfer-encoding: quoted-printable') === 0) {
|
||||
encodingQuotedPrintable = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// we found the headers and an empty newline marks the beginning of the body
|
||||
if (headers && parts[i] === '') {
|
||||
headers = false;
|
||||
consume = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (consume) {
|
||||
if (encodingQuotedPrintable) parts[i] = quotedPrintable.decode(parts[i]);
|
||||
content.push(parts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return content.join('\n');
|
||||
}
|
||||
|
||||
function ImapProbe(options) {
|
||||
assert(options && typeof options === 'object');
|
||||
|
||||
this._connection = new Imap(options);
|
||||
this._options = options;
|
||||
}
|
||||
|
||||
ImapProbe.prototype._fetchMessage = function (seq, callback) {
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
var message = {
|
||||
subject: null,
|
||||
body: null,
|
||||
from: null,
|
||||
to: null,
|
||||
multipartBoundry: null,
|
||||
seqno: null
|
||||
};
|
||||
|
||||
var f = this._connection.seq.fetch(seq + ':' + seq, {
|
||||
bodies: ['HEADER.FIELDS (TO)', 'HEADER.FIELDS (FROM)', 'HEADER.FIELDS (SUBJECT)', 'HEADER.FIELDS (CONTENT-TYPE)', 'TEXT'],
|
||||
struct: true
|
||||
});
|
||||
|
||||
f.on('message', function (msg, seqno) {
|
||||
message.seqno = seqno;
|
||||
|
||||
msg.on('body', function (stream, info) {
|
||||
var buffer = '';
|
||||
|
||||
stream.on('data', function (chunk) {
|
||||
buffer += chunk.toString('utf8');
|
||||
});
|
||||
|
||||
stream.once('end', function () {
|
||||
if (info.which === 'TEXT') {
|
||||
message.body = buffer;
|
||||
} else if (info.which === 'HEADER.FIELDS (SUBJECT)') {
|
||||
message.subject = Imap.parseHeader(buffer).subject;
|
||||
} else if (info.which === 'HEADER.FIELDS (FROM)') {
|
||||
message.from = Imap.parseHeader(buffer).from;
|
||||
} else if (info.which === 'HEADER.FIELDS (TO)') {
|
||||
message.to = Imap.parseHeader(buffer).to;
|
||||
} else if (info.which === 'HEADER.FIELDS (CONTENT-TYPE)') {
|
||||
if (buffer.indexOf('multipart/alternative') !== -1) {
|
||||
// extract boundary and remove any " or '
|
||||
message.multipartBoundry = buffer.split('boundary=')[1]
|
||||
.replace(/"([^"]+(?="))"/g, '$1')
|
||||
.replace(/'([^']+(?='))'/g, '$1')
|
||||
.replace(/\r\n/g, '');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
msg.once('attributes', function (attrs) {
|
||||
message.attributes = attrs;
|
||||
});
|
||||
|
||||
msg.once('end', function () {
|
||||
if (message.multipartBoundry) {
|
||||
message.body = parseMultipart(message.body, message.multipartBoundry);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
f.once('error', callback);
|
||||
f.once('end', function () { callback(null, message); });
|
||||
};
|
||||
|
||||
function searchMessage(message, needle) {
|
||||
assert.strictEqual(typeof message, 'object');
|
||||
assert.strictEqual(typeof needle, 'object');
|
||||
|
||||
debug('searchMessage : %s %s %s %j', message.seqno, message.from, message.to, message.subject);
|
||||
|
||||
if (needle.subject && message.subject[0].match(needle.subject) === null) return false;
|
||||
if (needle.body && message.body.match(needle.body) === null) return false;
|
||||
if (needle.to && message.to.match(needle.to) === null) return false;
|
||||
if (needle.from && message.from.match(needle.from) === null) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ImapProbe.prototype._scanBox = function (needle, callback) {
|
||||
var that = this;
|
||||
|
||||
this._connection.openBox(this._options.mailbox || 'INBOX', !!this._options.readOnly, function (error, box) {
|
||||
if (error) return callback(error);
|
||||
|
||||
debug('mailbox messages', box.messages);
|
||||
|
||||
// fetch one by one to have consistent seq numbers
|
||||
var matchedMessage = null;
|
||||
async.whilst(function cond() {
|
||||
return box.messages.total > 0;
|
||||
}, function (iteratorCallback) {
|
||||
that._fetchMessage(box.messages.total--, function (error, message) {
|
||||
if (error) return iteratorCallback(error);
|
||||
|
||||
if (!searchMessage(message, needle)) return iteratorCallback(); // continue to next message
|
||||
|
||||
matchedMessage = message;
|
||||
iteratorCallback(new Error('Found'));
|
||||
});
|
||||
}, function whilstDone(error) {
|
||||
if (error && error.message !== 'Found') return callback(error); // imap error
|
||||
|
||||
async.series([
|
||||
function moveToTash(done) {
|
||||
if (!matchedMessage || that._options.readOnly) return done();
|
||||
|
||||
that._connection.seq.move(matchedMessage.seqno, ['Trash'], done);
|
||||
},
|
||||
that._connection.closeBox.bind(that._connection)
|
||||
], function () {
|
||||
if (!matchedMessage) return callback(new Error('Not found'));
|
||||
|
||||
callback(null, matchedMessage);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
ImapProbe.prototype.probe = function (needle, callback) {
|
||||
var that = this;
|
||||
|
||||
var times = needle.times || 1, interval = needle.interval || 20000;
|
||||
|
||||
this._connection.once('error', callback);
|
||||
|
||||
this._connection.once('end', function() {
|
||||
debug('Connection ended');
|
||||
});
|
||||
|
||||
this._connection.once('ready', function () {
|
||||
debug('Connection success');
|
||||
|
||||
async.retry({ times: times, interval: interval }, that._scanBox.bind(that, needle), function (error, message) {
|
||||
that._connection.end(); // doesn't take a callback !
|
||||
callback(error, message);
|
||||
});
|
||||
});
|
||||
|
||||
this._connection.connect();
|
||||
};
|
@ -23,6 +23,7 @@
|
||||
"mocha": "^2.2.5",
|
||||
"once": "^1.3.3",
|
||||
"postmark": "^1.0.0",
|
||||
"quoted-printable": "^1.0.0",
|
||||
"readline-sync": "^1.2.19",
|
||||
"safetydance": "0.0.17",
|
||||
"semver": "^4.3.6",
|
||||
|
Loading…
Reference in New Issue
Block a user