From 02ef05586e88f6fbc43b5393e6bfc34f10452da6 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Sun, 18 Jan 2026 13:47:36 +0900 Subject: [PATCH] Add advanced Outlook search and logging features Introduces multiple search helper methods to the Outlook class, including sender/recipient/subject filters and date-based queries. Adds detailed logging for MAPI session management and folder selection. Implements ItemsFiltered for predicate-based filtering and refactors folder path resolution for improved robustness. --- lib/msoffice.js | 217 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 201 insertions(+), 16 deletions(-) diff --git a/lib/msoffice.js b/lib/msoffice.js index 2247743..c5a096d 100644 --- a/lib/msoffice.js +++ b/lib/msoffice.js @@ -237,19 +237,35 @@ function Outlook() { this.open = function () { try { this.namespace.Logon("", "", false, false); - } catch (e) {} + console.info("Outlook MAPI session established"); + } catch (e) { + // Already logged on / interactive session not required + console.warn("Outlook MAPI session already active or logon skipped"); + } + this.selectFolder(Outlook.Folders.Inbox); + console.info("Outlook folder selected: Inbox"); + return this; }; this.close = function () { this.items = null; this.currentFolder = null; - try { this.namespace.Logoff(); } catch (e) {} + + try { + this.namespace.Logoff(); + console.info("Outlook MAPI session closed"); + } catch (e) { + // Logoff may fail if Outlook manages the session + console.warn("Outlook MAPI session logoff skipped"); + } + this.namespace = null; this.application = null; + console.info("Outlook automation released"); }; - + this.selectFolder = function (folderIdOrPath) { if (typeof folderIdOrPath === "number") { this.currentFolder = this.namespace.GetDefaultFolder(folderIdOrPath); @@ -260,7 +276,7 @@ function Outlook() { } this.items = this.currentFolder.Items; - this.items.Sort("[ReceivedTime]", true); + this.items.Sort("[ReceivedTime]", true); // newest first return this; }; @@ -280,9 +296,44 @@ function Outlook() { }; this.createMail = function () { - var mail = this.application.CreateItem(0); + var mail = this.application.CreateItem(0); // 0 = olMailItem return new Outlook.MailItem(mail); }; + + // ----------------------------- + // Search helpers + // ----------------------------- + + this.searchBySenderContains = function (keyword) { + return this.restrict(Outlook.Search.filters.senderContains(keyword)); + }; + + this.searchByRecipientContains = function (keyword) { + return this.restrict(Outlook.Search.filters.recipientContains(keyword)); + }; + + this.searchBySenderOrRecipientContains = function (keyword) { + var pre = this.restrict(Outlook.Search.filters.senderOrToCcBccContains(keyword)); + return new Outlook.ItemsFiltered(pre, function (mailItem) { + return Outlook.Search.match.senderOrRecipientObjectContains(mailItem, keyword); + }); + }; + + this.searchBySenderEmailEquals = function (email) { + return this.restrict(Outlook.Search.filters.senderEmailEquals(email)); + }; + + this.searchUnread = function () { + return this.restrict("[Unread] = True"); + }; + + this.searchSince = function (dateObj) { + return this.restrict(Outlook.Search.filters.receivedSince(dateObj)); + }; + + this.searchSubjectContains = function (keyword) { + return this.restrict(Outlook.Search.filters.subjectContains(keyword)); + }; } Outlook.Folders = { @@ -297,15 +348,27 @@ Outlook.Folders = { Outlook.MailItemClass = 43; Outlook.resolveFolderPath = function (mapiNamespace, path) { + // path examples: + // - "Inbox\\SubFolder" + // - "Mailbox - Name\\Inbox\\SubFolder" (store root name) var parts = path.split("\\"); - var root = mapiNamespace.Folders.Item(1); - var cur = root; + var cur = null; - for (var i = 0; i < parts.length; i++) { - if (parts[i]) { - cur = cur.Folders.Item(parts[i]); + // If first segment matches a store root, start there; else start at default store root. + var stores = mapiNamespace.Folders; + for (var i = 1; i <= stores.Count; i++) { + var f = stores.Item(i); + if ((f.Name + "") === (parts[0] + "")) { + cur = f; + parts.shift(); + break; } } + if (!cur) cur = stores.Item(1); + + for (var j = 0; j < parts.length; j++) { + if (parts[j]) cur = cur.Folders.Item(parts[j]); + } return cur; }; @@ -318,23 +381,54 @@ Outlook.Items = function (items) { this.get = function (idx) { var it = this.items.Item(idx); - if (it.Class === Outlook.MailItemClass) { - return new Outlook.MailItem(it); - } + if (!it) return null; + if (it.Class === Outlook.MailItemClass) return new Outlook.MailItem(it); return new Outlook.Item(it); }; this.forEach = function (fn, maxCount) { var n = this.count(); - if (typeof maxCount === "number" && maxCount > 0 && maxCount < n) { - n = maxCount; - } + if (typeof maxCount === "number" && maxCount > 0 && maxCount < n) n = maxCount; + for (var i = 1; i <= n; i++) { fn(this.get(i), i); } }; }; +Outlook.ItemsFiltered = function (items, predicate) { + // items: Outlook.Items + this.base = (items instanceof Outlook.Items) ? items : new Outlook.Items(items); + this.predicate = predicate; + + this.count = function () { + // filtered count is expensive; do not compute + return this.base.count(); + }; + + this.get = function (idx) { + return this.base.get(idx); + }; + + this.forEach = function (fn, maxCount) { + var n = this.base.count(); + var emitted = 0; + + for (var i = 1; i <= n; i++) { + var it = this.base.get(i); + if (!it) continue; + + if (it instanceof Outlook.MailItem) { + if (this.predicate(it)) { + fn(it, i); + emitted++; + if (typeof maxCount === "number" && maxCount > 0 && emitted >= maxCount) break; + } + } + } + }; +}; + Outlook.Item = function (item) { this.item = item; @@ -424,8 +518,99 @@ Outlook.MailItem = function (mail) { }; }; +Outlook.Search = {}; + +Outlook.Search.filters = {}; + +Outlook.Search.filters._escape = function (s) { + return (s + "").replace(/'/g, "''"); +}; + +Outlook.Search.filters.subjectContains = function (keyword) { + var k = Outlook.Search.filters._escape(keyword); + return "([Subject] LIKE '%" + k + "%')"; +}; + +Outlook.Search.filters.senderContains = function (keyword) { + var k = Outlook.Search.filters._escape(keyword); + return "([SenderEmailAddress] LIKE '%" + k + "%') OR ([SenderName] LIKE '%" + k + "%')"; +}; + +Outlook.Search.filters.senderEmailEquals = function (email) { + var e = Outlook.Search.filters._escape(email); + return "([SenderEmailAddress] = '" + e + "')"; +}; + +Outlook.Search.filters.recipientContains = function (keyword) { + var k = Outlook.Search.filters._escape(keyword); + return "([To] LIKE '%" + k + "%') OR ([CC] LIKE '%" + k + "%') OR ([BCC] LIKE '%" + k + "%')"; +}; + +Outlook.Search.filters.senderOrToCcBccContains = function (keyword) { + var k = Outlook.Search.filters._escape(keyword); + return "(" + + "([SenderEmailAddress] LIKE '%" + k + "%') OR ([SenderName] LIKE '%" + k + "%')" + + " OR ([To] LIKE '%" + k + "%') OR ([CC] LIKE '%" + k + "%') OR ([BCC] LIKE '%" + k + "%')" + + ")"; +}; + +Outlook.Search.filters.receivedSince = function (dateObj) { + // Outlook filter date string is locale-dependent; use Date's default string. + // In WSH/JScript, Date string typically matches system locale and Outlook accepts it. + return "([ReceivedTime] >= '" + dateObj + "')"; +}; + +Outlook.Search.match = {}; + +Outlook.Search.match._contains = function (hay, needle) { + return (hay + "").toLowerCase().indexOf((needle + "").toLowerCase()) >= 0; +}; + +Outlook.Search.match.senderOrRecipientObjectContains = function (mailItem, keyword) { + var k = keyword + ""; + + if (Outlook.Search.match._contains(mailItem.getSenderEmailAddress() || "", k)) return true; + if (Outlook.Search.match._contains(mailItem.getSenderName() || "", k)) return true; + + if (Outlook.Search.match._contains(mailItem.mail.To || "", k)) return true; + if (Outlook.Search.match._contains(mailItem.mail.CC || "", k)) return true; + if (Outlook.Search.match._contains(mailItem.mail.BCC || "", k)) return true; + + var r = mailItem.mail.Recipients; + for (var i = 1; i <= r.Count; i++) { + var ri = r.Item(i); + if (Outlook.Search.match._contains(ri.Address || "", k)) return true; + if (Outlook.Search.match._contains(ri.Name || "", k)) return true; + } + + return false; +}; + Outlook.FileExtensions = FileTypes.getExtensionsByOpenWith("msoutlook"); +/* +EXAMPLE: + +var ol = new Outlook().open().selectFolder(Outlook.Folders.Inbox); + +// sender contains +ol.searchBySenderContains("amazon.com").forEach(function(m) { + console.log("[S] " + m.getSenderEmailAddress() + " | " + m.getSubject()); +}, 10); + +// recipient contains +ol.searchByRecipientContains("me@example.com").forEach(function(m) { + console.log("[R] " + (m.mail.To || "") + " | " + m.getSubject()); +}, 10); + +// sender OR recipient contains (includes Recipients collection check) +ol.searchBySenderOrRecipientContains("team@company.com").forEach(function(m) { + console.log("[SR] " + m.getSubject()); +}, 10); + +ol.close(); +*/ + exports.Excel = Excel; exports.PowerPoint = PowerPoint; exports.Word = Word;