From f42fbd4e74f58eef1b81c3c3b6f6616d89e8bb51 Mon Sep 17 00:00:00 2001 From: ClaraCrazy Date: Sun, 7 Dec 2025 16:11:42 +0100 Subject: [PATCH] probably fix things? --- application/helper.js | 28 +- application/imap-service.js | 486 +++++++++++++------------ application/mail-processing-service.js | 137 +++---- domain/mail-repository.js | 94 ++--- 4 files changed, 381 insertions(+), 364 deletions(-) diff --git a/application/helper.js b/application/helper.js index d1aa2aa..6dbdece 100644 --- a/application/helper.js +++ b/application/helper.js @@ -9,25 +9,26 @@ class Helper { */ purgeTimeStamp() { return moment() - .subtract(config.email.purgeTime.time, config.email.purgeTime.unit) - .toDate() + .subtract(config.email.purgeTime.time, config.email.purgeTime.unit) + .toDate() } /** - * Check if time difference between now and purgeTimeStamp is more than one day - * @param {Date} now + * Check if time difference between now and purgeTimeStamp is more than one day + * @param {number|Date} now * @param {Date} past * @returns {Boolean} */ moreThanOneDay(now, past) { const DAY_IN_MS = 24 * 60 * 60 * 1000; - if((now - past) / DAY_IN_MS >= 1){ - return true - } else { - return false - } + + const nowMs = now instanceof Date ? now.getTime() : now; + const pastMs = past instanceof Date ? past.getTime() : new Date(past).getTime(); + + return (nowMs - pastMs) >= DAY_IN_MS; } + /** * Convert time to highest possible unit (minutes, hours, days) where `time > 1` and `Number.isSafeInteger(time)` (whole number) * @param {Number} time @@ -43,7 +44,8 @@ class Helper { if (convertedTime > 60) { convertedTime = convertedTime / 60 convertedUnit = 'hours'; - }} + } + } if (convertedUnit === 'hours') { if (convertedTime > 24) { @@ -106,8 +108,8 @@ class Helper { */ shuffleFirstItem(array) { - let first = array[Math.floor(Math.random()*array.length)] - array = array.filter((value)=>value!=first); + let first = array[Math.floor(Math.random() * array.length)] + array = array.filter((value) => value != first); array = [first].concat(array) return array } @@ -144,4 +146,4 @@ class Helper { } } -module.exports = Helper +module.exports = Helper \ No newline at end of file diff --git a/application/imap-service.js b/application/imap-service.js index 09117be..49b2649 100644 --- a/application/imap-service.js +++ b/application/imap-service.js @@ -1,6 +1,6 @@ const EventEmitter = require('events') const imaps = require('imap-simple') -const {simpleParser} = require('mailparser') +const { simpleParser } = require('mailparser') const addressparser = require('nodemailer/lib/addressparser') const pSeries = require('p-series') const retry = require('async-retry') @@ -21,20 +21,20 @@ const helper = new(Helper) * @returns {undefined|Promise} Returns a promise when no callback is specified, resolving when the action succeeds. * @memberof ImapSimple */ -imaps.ImapSimple.prototype.deleteMessage = function (uid, callback) { +imaps.ImapSimple.prototype.deleteMessage = function(uid, callback) { var self = this; if (callback) { return nodeify(self.deleteMessage(uid), callback); } - return new Promise(function (resolve, reject) { - self.imap.addFlags(uid, '\\Deleted', function (err) { + return new Promise(function(resolve, reject) { + self.imap.addFlags(uid, '\\Deleted', function(err) { if (err) { reject(err); return; } - self.imap.expunge( function (err) { + self.imap.expunge(function(err) { if (err) { reject(err); return; @@ -53,10 +53,10 @@ imaps.ImapSimple.prototype.deleteMessage = function (uid, callback) { * @returns {undefined|Promise} Returns a promise when no callback is specified, resolving to `boxName` * @memberof ImapSimple */ -imaps.ImapSimple.prototype.closeBox = function (autoExpunge=true, callback) { +imaps.ImapSimple.prototype.closeBox = function(autoExpunge = true, callback) { var self = this; - if (typeof(autoExpunge) == 'function'){ + if (typeof(autoExpunge) == 'function') { callback = autoExpunge; autoExpunge = true; } @@ -65,9 +65,9 @@ imaps.ImapSimple.prototype.closeBox = function (autoExpunge=true, callback) { return nodeify(this.closeBox(autoExpunge), callback); } - return new Promise(function (resolve, reject) { + return new Promise(function(resolve, reject) { - self.imap.closeBox(autoExpunge, function (err, result) { + self.imap.closeBox(autoExpunge, function(err, result) { if (err) { reject(err); @@ -87,271 +87,285 @@ imaps.ImapSimple.prototype.closeBox = function (autoExpunge=true, callback) { * With this abstraction it would be easy to replace this with any inbound mail service like mailgun.com. */ class ImapService extends EventEmitter { - constructor(config) { - super() - this.config = config + constructor(config) { + super() + this.config = config - /** - * Set of emitted UIDs. Listeners should get each email only once. - * @type {Set} - */ - this.loadedUids = new Set() + /** + * Set of emitted UIDs. Listeners should get each email only once. + * @type {Set} + */ + this.loadedUids = new Set() - this.connection = null - this.initialLoadDone = false - } + this.connection = null + this.initialLoadDone = false + } - async connectAndLoadMessages() { - const configWithListener = { - ...this.config, - // 'onmail' adds a callback when new mails arrive. With this we can keep the imap refresh interval very low (or even disable it). - onmail: () => this._doOnNewMail() - } + async connectAndLoadMessages() { + const configWithListener = { + ...this.config, + // 'onmail' adds a callback when new mails arrive. With this we can keep the imap refresh interval very low (or even disable it). + onmail: () => this._doOnNewMail() + } - this.once(ImapService.EVENT_INITIAL_LOAD_DONE, () => - this._doAfterInitialLoad() - ) + this.once(ImapService.EVENT_INITIAL_LOAD_DONE, () => + this._doAfterInitialLoad() + ) - await this._connectWithRetry(configWithListener) + await this._connectWithRetry(configWithListener) - // Load all messages in the background. (ASYNC) - this._loadMailSummariesAndEmitAsEvents() - } + // Load all messages in the background. (ASYNC) + this._loadMailSummariesAndEmitAsEvents() + } - async _connectWithRetry(configWithListener) { - try { - await retry( - async _bail => { - // If anything throws, we retry - this.connection = await imaps.connect(configWithListener) + async _connectWithRetry(configWithListener) { + try { + await retry( + async _bail => { + // If anything throws, we retry + this.connection = await imaps.connect(configWithListener) - this.connection.on('error', err => { - // We assume that the app will be restarted after a crash. - console.error( - 'got fatal error during imap operation, stop app.', - err - ) - this.emit('error', err) - }) + this.connection.on('error', err => { + // We assume that the app will be restarted after a crash. + console.error( + 'got fatal error during imap operation, stop app.', + err + ) + this.emit('error', err) + }) - await this.connection.openBox('INBOX') - debug('connected to imap') - }, - { - retries: 5 - } - ) - } catch (error) { - console.error('can not connect, even with retry, stop app', error) - throw error - } - } + await this.connection.openBox('INBOX') + debug('connected to imap') + }, { + retries: 5 + } + ) + } catch (error) { + console.error('can not connect, even with retry, stop app', error) + throw error + } + } - _doOnNewMail() { - // Only react to new mails after the initial load, otherwise it might load the same mails twice. - if (this.initialLoadDone) { - this._loadMailSummariesAndEmitAsEvents() - } - } + _doOnNewMail() { + // Only react to new mails after the initial load, otherwise it might load the same mails twice. + if (this.initialLoadDone) { + this._loadMailSummariesAndEmitAsEvents() + } + } - _doAfterInitialLoad() { - // During initial load we ignored new incoming emails. In order to catch up with those, we have to refresh - // the mails once after the initial load. (async) - this._loadMailSummariesAndEmitAsEvents() + _doAfterInitialLoad() { + // During initial load we ignored new incoming emails. In order to catch up with those, we have to refresh + // the mails once after the initial load. (async) + this._loadMailSummariesAndEmitAsEvents() - // If the above trigger on new mails does not work reliable, we have to regularly check - // for new mails on the server. This is done only after all the mails have been loaded for the - // first time. (Note: set the refresh higher than the time it takes to download the mails). - if (this.config.imap.refreshIntervalSeconds) { - setInterval( - () => this._loadMailSummariesAndEmitAsEvents(), - this.config.imap.refreshIntervalSeconds * 1000 - ) - } - } + // If the above trigger on new mails does not work reliable, we have to regularly check + // for new mails on the server. This is done only after all the mails have been loaded for the + // first time. (Note: set the refresh higher than the time it takes to download the mails). + if (this.config.imap.refreshIntervalSeconds) { + setInterval( + () => this._loadMailSummariesAndEmitAsEvents(), + this.config.imap.refreshIntervalSeconds * 1000 + ) + } + } - async _loadMailSummariesAndEmitAsEvents() { - // UID: Unique id of a message. + async _loadMailSummariesAndEmitAsEvents() { + // UID: Unique id of a message. - const uids = await this._getAllUids() - const newUids = uids.filter(uid => !this.loadedUids.has(uid)) + const uids = await this._getAllUids() + const newUids = uids.filter(uid => !this.loadedUids.has(uid)) - // Optimize by fetching several messages (but not all) with one 'search' call. - // fetching all at once might be more efficient, but then it takes long until we see any messages - // in the frontend. With a small chunk size we ensure that we see the newest emails after a few seconds after - // restart. - const uidChunks = _.chunk(newUids, 20) + // Optimize by fetching several messages (but not all) with one 'search' call. + // fetching all at once might be more efficient, but then it takes long until we see any messages + // in the frontend. With a small chunk size we ensure that we see the newest emails after a few seconds after + // restart. + const uidChunks = _.chunk(newUids, 20) - // Creates an array of functions. We do not start the search now, we just create the function. - const fetchFunctions = uidChunks.map(uidChunk => () => - this._getMailHeadersAndEmitAsEvents(uidChunk) - ) + // Creates an array of functions. We do not start the search now, we just create the function. + const fetchFunctions = uidChunks.map(uidChunk => () => + this._getMailHeadersAndEmitAsEvents(uidChunk) + ) - await pSeries(fetchFunctions) + await pSeries(fetchFunctions) - if (!this.initialLoadDone) { - this.initialLoadDone = true - this.emit(ImapService.EVENT_INITIAL_LOAD_DONE) - } - } + if (!this.initialLoadDone) { + this.initialLoadDone = true + this.emit(ImapService.EVENT_INITIAL_LOAD_DONE) + } + } - /** - * - * @param {Date} deleteMailsBefore delete mails before this date instance - */ - async deleteOldMails(deleteMailsBefore) { - let uids = [] - //fetch mails from date +1day (calculated in MS) to avoid wasting resources and to fix imaps missing time-awareness - if (helper.moreThanOneDay(moment() + 24 * 60 * 60 * 1000, deleteMailsBefore)) { - uids = await this._searchWithoutFetch([ - ['!DELETED'], - ['BEFORE', deleteMailsBefore] - ]) - } else { - uids = await this._searchWithoutFetch([ - ['!DELETED'], - ]) - } + /** + * + * @param {Date} deleteMailsBefore delete mails before this date instance + */ + async deleteOldMails(deleteMailsBefore) { + let uids = [] + //fetch mails from date +1day (calculated in MS) to avoid wasting resources and to fix imaps missing time-awareness + if (helper.moreThanOneDay(moment(), deleteMailsBefore)) { + console.log("Deleting mails older than one day"); + uids = await this._searchWithoutFetch([ + ['!DELETED'], + ['BEFORE', deleteMailsBefore] + ]) + } else { + console.log("Deleting mails without date filter"); + uids = await this._searchWithoutFetch([ + ['!DELETED'], + ]) + } - if (uids.length === 0) { - return - } + if (uids.length === 0) { + return + } - const DeleteOlderThan = helper.purgeTimeStamp() - const uidsWithHeaders = await this._getMailHeaders(uids) + const DeleteOlderThan = helper.purgeTimeStamp() + const uidsWithHeaders = await this._getMailHeaders(uids) + console.log(`Fetched ${uidsWithHeaders.length} mails for deletion check.`); - uidsWithHeaders.forEach(mail => { - if (mail['attributes'].date > DeleteOlderThan || this.config.email.examples.uids.includes(parseInt(mail['attributes'].uid))) { - uids = uids.filter(uid => uid !== mail['attributes'].uid) - } - }) + uidsWithHeaders.forEach(mail => { + if (mail['attributes'].date > DeleteOlderThan || this.config.email.examples.uids.includes(parseInt(mail['attributes'].uid))) { + uids = uids.filter(uid => uid !== mail['attributes'].uid) + console.log(mail['attributes'].date > DeleteOlderThan ? `Mail UID: ${mail['attributes'].uid} is newer than purge time.` : `Mail UID: ${mail['attributes'].uid} is an example mail.`); + } + }) - if (uids.length === 0) { - debug('no mails to delete.') - return - } + if (uids.length === 0) { + console.log("Length 0") + debug('no mails to delete.') + return + } - debug(`deleting mails ${uids}`) - await this.connection.deleteMessage(uids) - uids.forEach(uid => this.emit(ImapService.EVENT_DELETED_MAIL, uid)) - console.log(`deleted ${uids.length} old messages.`) - } + debug(`deleting mails ${uids}`) + await this.connection.deleteMessage(uids) + uids.forEach(uid => { + this.emit(ImapService.EVENT_DELETED_MAIL, uid) + console.log(`UID deleted: ${uid}`); + }) + console.log(`deleted ${uids.length} old messages.`) + } - /** - * - * @param uid delete specific mail per UID - */ - async deleteSpecificEmail(uid) { - debug(`deleting mails ${uid}`) - if (!this.config.email.examples.uids.includes(parseInt(uid))) { - await this.connection.deleteMessage(uid) - console.log(`deleted mail with UID: ${uid}.`) - this.emit(ImapService.EVENT_DELETED_MAIL, uid) - } - } + /** + * + * @param uid delete specific mail per UID + */ + async deleteSpecificEmail(uid) { + debug(`deleting mails ${uid}`) + if (!this.config.email.examples.uids.includes(parseInt(uid))) { + await this.connection.deleteMessage(uid) + console.log(`deleted mail with UID: ${uid}.`) + this.emit(ImapService.EVENT_DELETED_MAIL, uid) + } + } - /** - * Helper method because ImapSimple#search also fetches each message. We just need the uids here. - * - * @param {Object} searchCriteria (see ImapSimple#search) - * @returns {Promise>} Array of UIDs - * @private - */ - async _searchWithoutFetch(searchCriteria) { - const imapUnderlying = this.connection.imap + /** + * Helper method because ImapSimple#search also fetches each message. We just need the uids here. + * + * @param {Object} searchCriteria (see ImapSimple#search) + * @returns {Promise>} Array of UIDs + * @private + */ + async _searchWithoutFetch(searchCriteria) { + const imapUnderlying = this.connection.imap - return new Promise((resolve, reject) => { - imapUnderlying.search(searchCriteria, (err, uids) => { - if (err) { - reject(err) - } else { - resolve(uids || []) - } - }) - }) - } + return new Promise((resolve, reject) => { + imapUnderlying.search(searchCriteria, (err, uids) => { + if (err) { + reject(err) + } else { + resolve(uids || []) + } + }) + }) + } - _createMailSummary(message) { - const headerPart = message.parts[0].body - const to = headerPart.to - .flatMap(to => addressparser(to)) - // The address also contains the name, just keep the email - .map(addressObj => addressObj.address) + _createMailSummary(message) { + const headerPart = message.parts[0].body + const to = headerPart.to + .flatMap(to => addressparser(to)) + // The address also contains the name, just keep the email + .map(addressObj => addressObj.address) - const from = headerPart.from.flatMap(from => addressparser(from)) + const from = headerPart.from.flatMap(from => addressparser(from)) - // Specify default subject, in case none exists. - let subject = "No Subject" - try { - subject = headerPart.subject[0] - } catch { - // Do nothing - } - const date = headerPart.date[0] - const {uid} = message.attributes + // Specify default subject, in case none exists. + let subject = "No Subject" + try { + subject = headerPart.subject[0] + } catch { + // Do nothing + } + const date = headerPart.date[0] + const { uid } = message.attributes - return Mail.create(to, from, date, subject, uid) - } + return Mail.create(to, from, date, subject, uid) + } - async fetchOneFullMail(to, uid, raw = false) { - if (!this.connection) { - // Here we 'fail fast' instead of waiting for the connection. - throw new Error('imap connection not ready') - } + async fetchOneFullMail(to, uid, raw = false) { + if (!this.connection) { + // Here we 'fail fast' instead of waiting for the connection. + throw new Error('imap connection not ready') + } - debug(`fetching full message ${uid}`) + debug(`fetching full message ${uid}`) - // For security we also filter TO, so it is harder to just enumerate all messages. - const searchCriteria = [['UID', uid], ['TO', to]] - const fetchOptions = { - bodies: ['HEADER', ''], // Empty string means full body - markSeen: false - } + // For security we also filter TO, so it is harder to just enumerate all messages. + const searchCriteria = [ + ['UID', uid], + ['TO', to] + ] + const fetchOptions = { + bodies: ['HEADER', ''], // Empty string means full body + markSeen: false + } - const messages = await this.connection.search(searchCriteria, fetchOptions) - if (messages.length === 0) { - return false - } else if (!raw) { - const fullBody = await _.find(messages[0].parts, {which: ''}) - return simpleParser(fullBody.body) - } else { - return messages[0].parts[1].body - } - } + const messages = await this.connection.search(searchCriteria, fetchOptions) + if (messages.length === 0) { + return false + } else if (!raw) { + const fullBody = await _.find(messages[0].parts, { which: '' }) + return simpleParser(fullBody.body) + } else { + return messages[0].parts[1].body + } + } - async _getAllUids() { - // We ignore mails that are flagged as DELETED, but have not been removed (expunged) yet. - const uids = await this._searchWithoutFetch([['!DELETED']]) - // Create copy to not mutate the original array. Sort with newest first (DESC). - return [...uids].sort().reverse() - } + async _getAllUids() { + // We ignore mails that are flagged as DELETED, but have not been removed (expunged) yet. + const uids = await this._searchWithoutFetch([ + ['!DELETED'] + ]) + // Create copy to not mutate the original array. Sort with newest first (DESC). + return [...uids].sort().reverse() + } - async _getMailHeadersAndEmitAsEvents(uids) { - try { - const mails = await this._getMailHeaders(uids) - mails.forEach(mail => { - this.loadedUids.add(mail.attributes.uid) - // Some broadcast messages have no TO field. We have to ignore those messages. - if (mail.parts[0].body.to) { - this.emit(ImapService.EVENT_NEW_MAIL, this._createMailSummary(mail)) - } - }) - } catch (error) { - debug('can not fetch', error) - throw error - } - } + async _getMailHeadersAndEmitAsEvents(uids) { + try { + const mails = await this._getMailHeaders(uids) + mails.forEach(mail => { + this.loadedUids.add(mail.attributes.uid) + // Some broadcast messages have no TO field. We have to ignore those messages. + if (mail.parts[0].body.to) { + this.emit(ImapService.EVENT_NEW_MAIL, this._createMailSummary(mail)) + } + }) + } catch (error) { + debug('can not fetch', error) + throw error + } + } - async _getMailHeaders(uids) { - const fetchOptions = { - envelope: true, - bodies: ['HEADER.FIELDS (FROM TO SUBJECT DATE)'], - struct: false - } - const searchCriteria = [['UID', ...uids]] - return this.connection.search(searchCriteria, fetchOptions) - } + async _getMailHeaders(uids) { + const fetchOptions = { + envelope: true, + bodies: ['HEADER.FIELDS (FROM TO SUBJECT DATE)'], + struct: false + } + const searchCriteria = [ + ['UID', ...uids] + ] + return this.connection.search(searchCriteria, fetchOptions) + } } // Consumers should use these constants: @@ -360,4 +374,4 @@ ImapService.EVENT_DELETED_MAIL = 'mailDeleted' ImapService.EVENT_INITIAL_LOAD_DONE = 'initial load done' ImapService.EVENT_ERROR = 'error' -module.exports = ImapService +module.exports = ImapService \ No newline at end of file diff --git a/application/mail-processing-service.js b/application/mail-processing-service.js index 2b63da3..5f1fd07 100644 --- a/application/mail-processing-service.js +++ b/application/mail-processing-service.js @@ -8,86 +8,87 @@ const helper = new(Helper) class MailProcessingService extends EventEmitter { - constructor(mailRepository, imapService, clientNotification, config) { - super() - this.mailRepository = mailRepository - this.clientNotification = clientNotification - this.imapService = imapService - this.config = config + constructor(mailRepository, imapService, clientNotification, config) { + super() + this.mailRepository = mailRepository + this.clientNotification = clientNotification + this.imapService = imapService + this.config = config - // Cached methods: - this.cachedFetchFullMail = mem( - this.imapService.fetchOneFullMail.bind(this.imapService), - {maxAge: 10 * 60 * 1000} - ) + // Cached methods: + this.cachedFetchFullMail = mem( + this.imapService.fetchOneFullMail.bind(this.imapService), { maxAge: 10 * 60 * 1000 } + ) - this.initialLoadDone = false + this.initialLoadDone = false - // Delete old messages now and every few hours - this.imapService.once(ImapService.EVENT_INITIAL_LOAD_DONE, () => - this._deleteOldMails() - ) - setInterval(() => this._deleteOldMails(), 10 * 60 * 1000) - } + // Delete old messages now and every few hours + this.imapService.once(ImapService.EVENT_INITIAL_LOAD_DONE, () => + this._deleteOldMails() + ) + setInterval(() => { + this._deleteOldMails() + }, 60 * 1000) + } - getMailSummaries(address) { - return this.mailRepository.getForRecipient(address) - } + getMailSummaries(address) { + return this.mailRepository.getForRecipient(address) + } - deleteSpecificEmail(adress, uid) { - if (this.mailRepository.removeUid(uid, adress) == true) { - this.imapService.deleteSpecificEmail(uid) - } - } + deleteSpecificEmail(adress, uid) { + if (this.mailRepository.removeUid(uid, adress) == true) { + this.imapService.deleteSpecificEmail(uid) + } + } - getOneFullMail(address, uid, raw = false) { - return this.cachedFetchFullMail(address, uid, raw) - } + getOneFullMail(address, uid, raw = false) { + return this.cachedFetchFullMail(address, uid, raw) + } - getAllMailSummaries() { - return this.mailRepository.getAll() - } + getAllMailSummaries() { + return this.mailRepository.getAll() + } - onInitialLoadDone() { - this.initialLoadDone = true - console.log( - `initial load done, got ${this.mailRepository.mailCount()} mails` - ) - } + onInitialLoadDone() { + this.initialLoadDone = true + console.log( + `initial load done, got ${this.mailRepository.mailCount()} mails` + ) + } - onNewMail(mail) { - if (this.initialLoadDone) { - // For now, only log messages if they arrive after the initial load - debug('new mail for', mail.to[0]) - } + onNewMail(mail) { + if (this.initialLoadDone) { + // For now, only log messages if they arrive after the initial load + debug('new mail for', mail.to[0]) + } - mail.to.forEach(to => { - this.mailRepository.add(to, mail) - return this.clientNotification.emit(to) - }) - } + mail.to.forEach(to => { + this.mailRepository.add(to, mail) + return this.clientNotification.emit(to) + }) + } - onMailDeleted(uid) { - debug('mail deleted with uid', uid) - this.mailRepository.removeUid(uid) - } + onMailDeleted(uid) { + debug('mail deleted with uid', uid) + this.mailRepository.removeUid(uid) + } - async _deleteOldMails() { - try { - await this.imapService.deleteOldMails(helper.purgeTimeStamp()) - } catch (error) { - console.log('can not delete old messages', error) - } - } + async _deleteOldMails() { + try { + await this.imapService.deleteOldMails(helper.purgeTimeStamp()) + } catch (error) { + console.log('can not delete old messages', error) + } + } - _saveToFile(mails, filename) { - const fs = require('fs') - fs.writeFile(filename, JSON.stringify(mails), err => { - if (err) { - console.error('can not save mails to file', err) - } - }) - } + _saveToFile(mails, filename) { + const fs = require('fs') + fs.writeFile(filename, JSON.stringify(mails), err => { + if (err) { + console.error('can not save mails to file', err) + } + }) + } } -module.exports = MailProcessingService +module.exports = MailProcessingService \ No newline at end of file diff --git a/domain/mail-repository.js b/domain/mail-repository.js index 149b3f0..d8d7585 100644 --- a/domain/mail-repository.js +++ b/domain/mail-repository.js @@ -4,57 +4,57 @@ const _ = require('lodash') const config = require('../application/config') class MailRepository { - constructor() { - // MultiMap docs: https://yomguithereal.github.io/mnemonist/multi-map - this.mailSummaries = new MultiMap() - this.config = config - } + constructor() { + // MultiMap docs: https://yomguithereal.github.io/mnemonist/multi-map + this.mailSummaries = new MultiMap() + this.config = config + } - getForRecipient(address) { - let mails = this.mailSummaries.get(address) || [] - mails.forEach(mail => { - if (mail.to == this.config.email.examples.account && !this.config.email.examples.uids.includes(parseInt(mail.uid))) { - mails = mails.filter(m => m.uid != mail.uid) - debug('prevented non-example email from being shown in example inbox', mail.uid) - } - }) - return _.orderBy(mails, mail => Date.parse(mail.date), ['desc']) - } + getForRecipient(address) { + let mails = this.mailSummaries.get(address) || [] + mails.forEach(mail => { + if (mail.to == this.config.email.examples.account && !this.config.email.examples.uids.includes(parseInt(mail.uid))) { + mails = mails.filter(m => m.uid != mail.uid) + console.log('prevented non-example email from being shown in example inbox', mail.uid) + } + }) + return _.orderBy(mails, mail => Date.parse(mail.date), ['desc']) + } - getAll() { - const mails = [...this.mailSummaries.values()] - return _.orderBy(mails, mail => Date.parse(mail.date), ['desc']) - } + getAll() { + const mails = [...this.mailSummaries.values()] + return _.orderBy(mails, mail => Date.parse(mail.date), ['desc']) + } - add(to, mailSummary) { - if (to !== undefined) { - this.mailSummaries.set(to.toLowerCase(), mailSummary) - } else { - debug('IMAP reported no recipient for mail, ignoring', mailSummary) - } - } + add(to, mailSummary) { + if (to !== undefined) { + this.mailSummaries.set(to.toLowerCase(), mailSummary) + } else { + debug('IMAP reported no recipient for mail, ignoring', mailSummary) + } + } - removeUid(uid, address) { - if (!this.config.email.examples.uids.includes(parseInt(uid))) { - var deleted = false - // TODO: make this more efficient, looping through each email is not cool. - this.mailSummaries.forEachAssociation((mails, to) => { - mails - .filter(mail => mail.uid === parseInt(uid) && (address ? to == address : true)) - .forEach(mail => { - this.mailSummaries.remove(to, mail) - debug('removed ', mail.date, to, mail.subject) - deleted = true - }) - }) - return deleted - } - return false - } + removeUid(uid, address) { + if (!this.config.email.examples.uids.includes(parseInt(uid))) { + var deleted = false + // TODO: make this more efficient, looping through each email is not cool. + this.mailSummaries.forEachAssociation((mails, to) => { + mails + .filter(mail => mail.uid === parseInt(uid) && (address ? to == address : true)) + .forEach(mail => { + this.mailSummaries.remove(to, mail) + debug('removed ', mail.date, to, mail.subject) + deleted = true + }) + }) + return deleted + } + return false + } - mailCount() { - return this.mailSummaries.size - } + mailCount() { + return this.mailSummaries.size + } } -module.exports = MailRepository +module.exports = MailRepository \ No newline at end of file