diff --git a/.gitignore b/.gitignore index 40b878d..08f6184 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -node_modules/ \ No newline at end of file +node_modules/ +npm-debug.log +attachments/ \ No newline at end of file diff --git a/LICENSE b/LICENSE index cdad213..e94ed42 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,6 @@ -Copyright (c) 2012-2014 Chirag Jain +Copyright (c) 2012-2014 Chirag Jain (mail-listener2) +Pranav Dakshinamurthy 2014-2018 (mail-listener4) +Matej Malicek 2019 (mail-listener5) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/index.js b/index.js index 183c911..3775572 100644 --- a/index.js +++ b/index.js @@ -1,148 +1,147 @@ +/**@module mail-listener5 + * @author Matej Malicek + * @version 1.0.0 + * @date 4 March 2019 + */ + + // Require statements var Imap = require('imap'); -var util = require('util'); var EventEmitter = require('events').EventEmitter; -var MailParser = require("mailparser").MailParser; -var fs = require("fs"); +var simpleParser = require('mailparser').simpleParser; +var fs = require('fs'); var path = require('path'); var async = require('async'); -module.exports = MailListener; - -function MailListener(options) { - this.markSeen = !! options.markSeen; - this.mailbox = options.mailbox || "INBOX"; - if ('string' === typeof options.searchFilter) { - this.searchFilter = [options.searchFilter]; - } else { - this.searchFilter = options.searchFilter || ["UNSEEN"]; - } - this.fetchUnreadOnStart = !! options.fetchUnreadOnStart; - this.mailParserOptions = options.mailParserOptions || {}; - if (options.attachments && options.attachmentOptions && options.attachmentOptions.stream) { - this.mailParserOptions.streamAttachments = true; +class MailListener extends EventEmitter { + constructor(options) { + super(); + this.markSeen = !! options.markSeen; + this.mailbox = options.mailbox || 'INBOX'; + if ('string' === typeof options.searchFilter) + { + this.searchFilter = [options.searchFilter]; + } + else + { + this.searchFilter = options.searchFilter || ['UNSEEN']; + } + this.fetchUnreadOnStart = !! options.fetchUnreadOnStart; + this.mailParserOptions = options.mailParserOptions || {}; + if (options.attachments && options.attachmentOptions && options.attachmentOptions.stream) + { + this.mailParserOptions.streamAttachments = true; + } + this.attachmentOptions = options.attachmentOptions || {}; + this.attachments = options.attachments || false; + this.attachmentOptions.directory = (this.attachmentOptions.directory ? this.attachmentOptions.directory : ''); + this.imap = new Imap({ + xoauth2: options.xoauth2, + user: options.username, + password: options.password, + host: options.host, + port: options.port, + tls: options.tls, + tlsOptions: options.tlsOptions || {}, + connTimeout: options.connTimeout || null, + authTimeout: options.authTimeout || null, + debug: options.debug || null + }); + this.imap.once('ready', this.imapReady.bind(this)); + this.imap.once('close', this.imapClose.bind(this)); + this.imap.on('error', this.imapError.bind(this)); } - this.attachmentOptions = options.attachmentOptions || {}; - this.attachments = options.attachments || false; - this.attachmentOptions.directory = (this.attachmentOptions.directory ? this.attachmentOptions.directory : ''); - this.imap = new Imap({ - xoauth2: options.xoauth2, - user: options.username, - password: options.password, - host: options.host, - port: options.port, - tls: options.tls, - tlsOptions: options.tlsOptions || {}, - connTimeout: options.connTimeout || null, - authTimeout: options.authTimeout || null, - debug: options.debug || null - }); - - this.imap.once('ready', imapReady.bind(this)); - this.imap.once('close', imapClose.bind(this)); - this.imap.on('error', imapError.bind(this)); -} -util.inherits(MailListener, EventEmitter); - -MailListener.prototype.start = function() { - this.imap.connect(); -}; + start() { + this.imap.connect(); + } -MailListener.prototype.stop = function() { - this.imap.end(); -}; + stop() { + this.imap.connect(); + } -function imapReady() { - var self = this; - this.imap.openBox(this.mailbox, false, function(err, mailbox) { - if (err) { - self.emit('error', err); - } else { - self.emit('server:connected'); - if (self.fetchUnreadOnStart) { - parseUnread.call(self); + imapReady() { + this.imap.openBox(this.mailbox, false, (error, mailbox) => { + if (error) + { + this.emit('error', error); } - var listener = imapMail.bind(self); - self.imap.on('mail', listener); - self.imap.on('update', listener); - } - }); -} - -function imapClose() { - this.emit('server:disconnected'); -} + else + { + this.emit('server:connected'); + this.emit('mailbox', mailbox); + if (this.fetchUnreadOnStart) + { + this.parseUnread.call(this); + } + let listener = this.imapMail.bind(this); + this.imap.on('mail', listener); + this.imap.on('update', listener); + } + }); + } -function imapError(err) { - this.emit('error', err); -} + imapClose() { + this.emit('server:disconnected'); + } -function imapMail() { - parseUnread.call(this); -} + imapError(error) { + this.emit('error', error); + } -function parseUnread() { - var self = this; - this.imap.search(self.searchFilter, function(err, results) { - if (err) { - self.emit('error', err); - } else if (results.length > 0) { - async.each(results, function( result, callback) { - var f = self.imap.fetch(result, { - bodies: '', - markSeen: self.markSeen - }); - f.on('message', function(msg, seqno) { - var parser = new MailParser(self.mailParserOptions); - var attributes = null; - var emlbuffer = new Buffer(''); + imapMail() { + this.parseUnread.call(this); + } - parser.on("end", function(mail) { - mail.eml = emlbuffer.toString('utf-8'); - if (!self.mailParserOptions.streamAttachments && mail.attachments && self.attachments) { - async.each(mail.attachments, function( attachment, callback) { - fs.writeFile(self.attachmentOptions.directory + attachment.generatedFileName, attachment.content, function(err) { - if(err) { - self.emit('error', err); - callback() - } else { - attachment.path = path.resolve(self.attachmentOptions.directory + attachment.generatedFileName); - self.emit('attachment', attachment); - callback() - } - }); - }, function(err){ - self.emit('mail', mail, seqno, attributes); - callback() - }); - } else { - self.emit('mail',mail,seqno,attributes); - } - }); - parser.on("attachment", function (attachment) { - self.emit('attachment', attachment); + parseUnread() { + let self = this; + self.imap.search(self.searchFilter, (error, results) => { + if (error) + { + self.emit('error', err); + } + else if (results.length > 0) + { + async.each(results, (result, callback) => { + let f = self.imap.fetch(result, { + bodies: '', + markSeen: self.markSeen }); - msg.on('body', function(stream, info) { - stream.on('data', function(chunk) { - emlbuffer = Buffer.concat([emlbuffer, chunk]); - }); - stream.once('end', function() { - parser.write(emlbuffer); - parser.end(); + f.on('message', (msg, seqno) => { + msg.on('body', async (stream, info) => { + let parsed = await simpleParser(stream); + self.emit('mail', parsed, seqno); + self.emit('headers', parsed.headers, seqno); + self.emit('body', {html: parsed.html, text: parsed.text, textAsHtml: parsed.textAsHtml}, seqno); + if (parsed.attachments.length>0) + { + for (let att of parsed.attachments) + { + if (self.attachments) + { + await fs.writeFile(`${self.attachmentOptions.directory}${att.filename}`, att.content, (error) =>{ + self.emit('error', error); + }); + self.emit('attachment', att, `${self.attachmentOptions.directory}${att.filename}`, seqno); + } + else + { + self.emit('attachment', att, null, seqno); + } + } + } }); }); - msg.on('attributes', function(attrs) { - attributes = attrs; + f.once('error', (error) => { + self.emit('error', error); }); + }, (error) => { + if (error) + { + self.emit('error', error); + } }); - f.once('error', function(err) { - self.emit('error', err); - }); - }, function(err){ - if( err ) { - self.emit('error', err); - } - }); - } - }); -} + } + }); + } +}; +module.exports.MailListener = MailListener; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9343de9 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,286 @@ +{ + "name": "mail-listener5", + "version": "2.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "requires": { + "lodash": "^4.17.14" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + } + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + }, + "html-to-text": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-4.0.0.tgz", + "integrity": "sha512-QQl5EEd97h6+3crtgBhkEAO6sQnZyDff8DAeJzoSkOc1Dqe1UvTUZER0B+KjBe6fPZqq549l2VUhtracus3ndA==", + "requires": { + "he": "^1.0.0", + "htmlparser2": "^3.9.2", + "lodash": "^4.17.4", + "optimist": "^0.6.1" + } + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "readable-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.2.0.tgz", + "integrity": "sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", + "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "imap": { + "version": "0.8.19", + "resolved": "https://registry.npmjs.org/imap/-/imap-0.8.19.tgz", + "integrity": "sha1-NniHOTSrCc6mukh0HyhNoq9Z2NU=", + "requires": { + "readable-stream": "1.1.x", + "utf7": ">=1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "libbase64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-1.0.3.tgz", + "integrity": "sha512-ULQZAATVGTAgVNwP61R+MbbSGNBy1tVzWupB9kbE6p+VccWd+J+ICXgOwQic5Yqagzpu+oPZ8sI7yXdWJnPPkA==" + }, + "libmime": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/libmime/-/libmime-4.0.1.tgz", + "integrity": "sha512-mGgJLRkpkMxZZYE7ncVXokgKfi5ePrIB1H3W/Bv3GbkVnFydIHTsPrfAVW0edxalQHmFfqDMU9W45PidCLG6DA==", + "requires": { + "iconv-lite": "0.4.23", + "libbase64": "1.0.3", + "libqp": "1.1.0" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, + "libqp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/libqp/-/libqp-1.1.0.tgz", + "integrity": "sha1-9ebgatdLeU+1tbZpiL9yjvHe2+g=" + }, + "linkify-it": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.0.3.tgz", + "integrity": "sha1-2UpGSPmxwXnWT6lykSaL22zpQ08=", + "requires": { + "uc.micro": "^1.0.1" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "mailparser": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-2.4.3.tgz", + "integrity": "sha512-VKfFVzpgXLL7fNYy2ryRCPzwBxdlWdnExAgS551bQrmDuJgP0pmE01f9eIGKXlRpsCB8xw7XwYTkWqHBGEEbNg==", + "requires": { + "he": "1.2.0", + "html-to-text": "4.0.0", + "iconv-lite": "0.4.24", + "libmime": "4.0.1", + "linkify-it": "2.0.3", + "mailsplit": "4.2.4", + "nodemailer": "4.6.8", + "tlds": "1.203.1" + } + }, + "mailsplit": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/mailsplit/-/mailsplit-4.2.4.tgz", + "integrity": "sha512-UZUSCv2RtjUMbPFRkECF2O9IIdquEbMNlvIIlEgDROt+HVa++vFqqZZLuu/3MDFt5AND6W/r1a1UfJt7pA1f+Q==", + "requires": { + "libbase64": "1.0.3", + "libmime": "4.0.1", + "libqp": "1.1.0" + } + }, + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" + }, + "nodemailer": { + "version": "4.6.8", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-4.6.8.tgz", + "integrity": "sha512-A3s7EM/426OBIZbLHXq2KkgvmKbn2Xga4m4G+ZUA4IaZvG8PcZXrFh+2E4VaS2o+emhuUVRnzKN2YmpkXQ9qwA==" + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + } + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "tlds": { + "version": "1.203.1", + "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.203.1.tgz", + "integrity": "sha512-7MUlYyGJ6rSitEZ3r1Q1QNV8uSIzapS8SmmhSusBuIc7uIxPPwsKllEP0GRp1NS6Ik6F+fRZvnjDWm3ecv2hDw==" + }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" + }, + "utf7": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utf7/-/utf7-1.0.2.tgz", + "integrity": "sha1-lV9JCq5lO6IguUVqCod2wZk2CZE=", + "requires": { + "semver": "~5.3.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + } + } +} diff --git a/package.json b/package.json index 253e5a4..41296ae 100644 --- a/package.json +++ b/package.json @@ -1,29 +1,37 @@ { - "name": "mail-listener2", - "version": "0.3.1", + "name": "mail-listener5", + "version": "2.0.0", "description": "Mail listener library for node.js. Get notification when new email arrived.", "dependencies": { - "imap": "~0.8.14", - "mailparser": "~0.4.6", - "async": "^0.9.0" + "imap": "^0.8.19", + "mailparser": "^2.4.3", + "async": "^2.6.3" }, "repository": { "type": "git", - "url": "git://github.com/chirag04/mail-listener2.git" + "url": "git://github.com/MatejMalicek/mail-listener2.git" }, - "homepage": "https://github.com/chirag04/mail-listener2", + "homepage": "https://github.com/MatejMalicek/mail-listener2", "keywords": [ "mail", "job", "imap", "mail listener", + "mail listener2", + "mail listener3", + "mail listener4", "email", - "email parser" + "email parser", + "node imap", + "mail parser" ], - "author": { - "name": "Chirag Jain", - "email": "jain_chirag04@yahoo.com", - "url": "http://chiragjain.tumblr.com" + "author": "Matej Malicek ", + "license": "MIT", + "bugs": { + "url": "https://github.com/MatejMalicek/mail-listener2/issues" }, - "license": "MIT" + "main": "index.js", + "scripts": { + "test": "test.js" + } } diff --git a/readme.md b/readme.md index 2f46edb..4dbc7ab 100644 --- a/readme.md +++ b/readme.md @@ -1,16 +1,38 @@ # Overview -Mail-listener2 library for node.js. Get notification when new email arrived to inbox or when message metadata (e.g. flags) changes externally. Uses IMAP protocol. +Mail-listener5 library for node.js. Get notification when new email arrived to inbox or when message metadata (e.g. flags) changes externally. Uses IMAP protocol. + +## Version Notes +THIS PACKAGE IS STILL UNDERGOING MORE THOROUGH TESTING AND IMPROVEMENT. Expect further commits as functionality is added. :-) + +This package has several improvements and fixes over the mail-listener2 & mail-listener4. Most of the improvements are designed to improve security & usability, plus avoid deprecation warnings. The previous mail-listener packages used a now-deprecated version of MailParser and unsafe buffer constructors (see change notes below). + +This package uses the simpleParser function in NodeMailer. This parser is easier to implement & provides a Mail object from which any needed attributes can be extracted. However, it is more resource-intensive when it comes to larger emails, as attachments are not handled as streams, but rather are buffered in memory. In a future version, I plan to reintroduce the ability to stream attachments directly (rather than buffering them) so that larger attachments can be processed with fewer resources. + +Change notes: + + - Updating dependencies to newer versions, with security enhancements, etc. The previous mail-listeners all used now-deprecated versions of dependencies, many of which posed security problems as they used unsafe Buffer constructors (e.g. new Buffer() - see https://nodejs.org/en/docs/guides/buffer-constructor-deprecation/). + - Updating code to use ES6 classes. The previous version used util.inherits(), which is now discouraged (see https://nodejs.org/dist/latest-v10.x/docs/api/util.html#util_util_inherits_constructor_superconstructor). + - Updating code to use lexical variable declarations where appropriate. + - Updating code to use ES6 arrow functions within methods where appropriate. + - Updating test.js to use environment variables for credentials, etc (see new [Testing](#Testing) section below). We are using these libraries: [node-imap](https://github.com/mscdex/node-imap), [mailparser](https://github.com/andris9/mailparser). -Heavily inspired by [mail-listener](https://github.com/circuithub/mail-listener). +Heavily inspired by [mail-listener2](https://github.com/chirag04/mail-listener2) and [mail-listener5](https://github.com/Pranav-Dakshina/mail-listener2). + +NOTE: This version is designed to work with & tested on NodeJS v 10.15.2 LTS, the most recent LTS version as at March 2019. It might not work on older versions of Node. + +## Planned Future Improvements +Whilst this package is confirmed to work, the ability to stream attachments (present in the older versions of mail-listener) has been taken out, mainly because the MailParser library has changed significantly & a substantial amount of refactoring is required in order to allow the safe streaming of attachments (which may contain untrusted content). + +A future version will reintroduce this capability once the refactoring is complete. That version will allow attachments to be streamed directly to functions. At present, attachments are either saved to a file for later processing (if that option is selected) or an 'attachment' event is emitted, which contains a Buffer with the attachment content. This Buffer can then be processed as needed. ## Use Install -`npm install mail-listener2` +`npm install mail-listener5` JavaScript Code: @@ -18,7 +40,7 @@ JavaScript Code: ```javascript -var MailListener = require("mail-listener2"); +var MailListener = require("mail-listener5"); var mailListener = new MailListener({ username: "imap-username", @@ -31,10 +53,9 @@ var mailListener = new MailListener({ debug: console.log, // Or your custom function with only one incoming argument. Default: null tlsOptions: { rejectUnauthorized: false }, mailbox: "INBOX", // mailbox to monitor - searchFilter: ["UNSEEN", "FLAGGED"], // the search filter being used after an IDLE notification has been retrieved + searchFilter: ["ALL"], // the search filter being used after an IDLE notification has been retrieved markSeen: true, // all fetched email willbe marked as seen and not fetched next time fetchUnreadOnStart: true, // use it only if you want to get all unread email on lib start. Default is `false`, - mailParserOptions: {streamAttachments: true}, // options to be passed to mailParser lib. attachments: true, // download attachments as they are encountered to the project directory attachmentOptions: { directory: "attachments/" } // specify a download directory for attachments }); @@ -48,6 +69,10 @@ mailListener.on("server:connected", function(){ console.log("imapConnected"); }); +mailListener.on("mailbox", function(mailbox){ + console.log("Total number of mails: ", mailbox.messages.total); // this field in mailbox gives the total number of emails +}); + mailListener.on("server:disconnected", function(){ console.log("imapDisconnected"); }); @@ -56,16 +81,22 @@ mailListener.on("error", function(err){ console.log(err); }); -mailListener.on("mail", function(mail, seqno, attributes){ - // do something with mail object including attachments - console.log("emailParsed", mail); - // mail processing code goes here +mailListener.on("headers", function(headers, seqno){ + // do something with mail headers }); -mailListener.on("attachment", function(attachment){ - console.log(attachment.path); +mailListener.on("body", function(body, seqno){ + // do something with mail body +}) + +mailListener.on("attachment", function(attachment, path, seqno){ + // do something with attachment }); +mailListener.on("mail", function(mail, seqno) { + // do something with the whole email as a single object +}) + // it's possible to access imap object from node-imap library for performing additional actions. E.x. mailListener.imap.move(:msguids, :mailboxes, function(){}) @@ -74,12 +105,24 @@ mailListener.imap.move(:msguids, :mailboxes, function(){}) That's easy! ## Attachments -Attachments can be streamed or buffered. This feature is based on how [mailparser](https://github.com/andris9/mailparser#attachments) handles attachments. +Attachments in this version are buffered. This feature is based on how [mailparser](https://github.com/andris9/mailparser#attachments)'s simpleParser function handles attachments. Setting `attachments: true` will download attachments as buffer objects by default to the project directory. A specific download directory may be specified by setting `attachmentOptions: { directory: "attachments/"}`. -Attachments may also be streamed using `attachmentOptions: { stream: "true"}`. The `"attachment"` event will be fired every time an attachment is encountered. -Refer to the [mailparser docs](https://github.com/andris9/mailparser#attachment-streaming) for specifics on how to stream attachments. +The `"attachment"` event will be fired every time an attachment is encountered. +## Testing +A test script is available at test.js. Before using the test script, it is necessary to set the following environment variables: + - IMAPUSER - IMAP account username. + - IMAPPASS - IMAP account password. + - IMAPHOST - IMAP server hostname (e.g. imap.example.com). + +The test script assumes that the IMAP host supports TLS and that the port is the usual 993. These values can be changed in test.js if necessary. + +To run the test script, simply execute: + +```bash +export IMAPUSER='user@example.com' IMAPPASS='password' IMAPHOST='imap.example.com'; node test.js +``` ## License diff --git a/test.js b/test.js index c22f3b5..cdae320 100644 --- a/test.js +++ b/test.js @@ -1,16 +1,20 @@ -var MailListener = require("./"); +var MailListener = require("./index.js").MailListener; var mailListener = new MailListener({ - username: "xxxx", - password: "xxx", - host: "imap.gmail.com", + username: process.env.IMAPUSER, + password: process.env.IMAPPASS, + host: process.env.IMAPHOST, port: 993, tls: true, + connTimeout: 10000, // Default by node-imap + authTimeout: 5000, // Default by node-imap, + debug: null, // Or your custom function with only one incoming argument. Default: null tlsOptions: { rejectUnauthorized: false }, - mailbox: "INBOX", - markSeen: true, - fetchUnreadOnStart: true, - attachments: true, + mailbox: "INBOX", // mailbox to monitor + searchFilter: ["ALL"], // the search filter being used after an IDLE notification has been retrieved + markSeen: true, // all fetched email willbe marked as seen and not fetched next time + fetchUnreadOnStart: true, // use it only if you want to get all unread email on lib start. Default is `false`, + attachments: true, // download attachments as they are encountered to the project directory attachmentOptions: { directory: "attachments/" } }); @@ -20,6 +24,10 @@ mailListener.on("server:connected", function(){ console.log("imapConnected"); }); +mailListener.on("mailbox", function(mailbox){ + console.log("Total number of mails: ", mailbox.messages.total); +}); + mailListener.on("server:disconnected", function(){ console.log("imapDisconnected"); }); @@ -28,10 +36,18 @@ mailListener.on("error", function(err){ console.log(err); }); -mailListener.on("mail", function(mail){ - console.log(mail); +mailListener.on("headers", function(headers, seqno){ + console.log(`Email#${seqno} headers: `, headers); }); -mailListener.on("attachment", function(attachment){ - console.log(attachment); +mailListener.on("body", function(body, seqno) { + console.log(`Email#${seqno} body: `, body); +}) + +mailListener.on("attachment", function(attachment, path, seqno){ + console.log(`Email#${seqno} Attachment stored at: `, path); }); + +mailListener.on("mail", function(mail, seqno) { + console.log(`Email#${seqno} - entire parsed object: `, mail); +})