From 8f02a5f07cb98fcc4d4b2bd5036680000799f77e Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Sun, 18 Jan 2026 14:51:24 +0900 Subject: [PATCH] Refactor Outlook search filters and merge logic Updated Outlook search methods to use DASL filters for recipient searches and introduced a new ItemsMerged class to combine results from multiple Restrict calls. Cleaned up comments and improved clarity in filter functions, ensuring sender and recipient searches are handled separately and merged in JavaScript. --- lib/msoffice.js | 93 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 31 deletions(-) diff --git a/lib/msoffice.js b/lib/msoffice.js index 46905fe..f39568a 100644 --- a/lib/msoffice.js +++ b/lib/msoffice.js @@ -239,7 +239,6 @@ function Outlook() { this.namespace.Logon("", "", false, false); 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"); } @@ -257,7 +256,6 @@ function Outlook() { 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"); } @@ -265,7 +263,7 @@ function Outlook() { this.application = null; console.info("Outlook automation released"); }; - + this.selectFolder = function (folderIdOrPath) { if (typeof folderIdOrPath === "number") { this.currentFolder = this.namespace.GetDefaultFolder(folderIdOrPath); @@ -276,7 +274,7 @@ function Outlook() { } this.items = this.currentFolder.Items; - this.items.Sort("[ReceivedTime]", true); // newest first + this.items.Sort("[ReceivedTime]", true); return this; }; @@ -291,12 +289,14 @@ function Outlook() { }; this.restrict = function (filter) { + // Accept both Jet filter and DASL filter (@SQL=...) + console.log(filter); var restricted = this.items.Restrict(filter); return new Outlook.Items(restricted); }; this.createMail = function () { - var mail = this.application.CreateItem(0); // 0 = olMailItem + var mail = this.application.CreateItem(0); return new Outlook.MailItem(mail); }; @@ -309,12 +309,20 @@ function Outlook() { }; this.searchByRecipientContains = function (keyword) { - return this.restrict(Outlook.Search.filters.recipientContains(keyword)); + // DASL (@SQL=...) + return this.restrict(Outlook.Search.filters.recipientContains_DASL(keyword)); }; this.searchBySenderOrRecipientContains = function (keyword) { - var pre = this.restrict(Outlook.Search.filters.senderOrToCcBccContains(keyword)); - return new Outlook.ItemsFiltered(pre, function (mailItem) { + // IMPORTANT: cannot mix Jet and DASL in a single Restrict filter + // We run two Restrict calls and then merge the candidates in JS. + + var bySender = this.restrict(Outlook.Search.filters.senderContains_Jet(keyword)); + var byRecipients = this.restrict(Outlook.Search.filters.recipientContains_DASL(keyword)); + + var merged = new Outlook.ItemsMerged(bySender, byRecipients); + + return new Outlook.ItemsFiltered(merged, function (mailItem) { return Outlook.Search.match.senderOrRecipientObjectContains(mailItem, keyword); }); }; @@ -348,13 +356,9 @@ 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 cur = null; - // 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); @@ -397,12 +401,10 @@ Outlook.Items = function (items) { }; 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(); }; @@ -429,6 +431,51 @@ Outlook.ItemsFiltered = function (items, predicate) { }; }; +Outlook.ItemsMerged = function (a, b) { + this.a = (a instanceof Outlook.Items) ? a : new Outlook.Items(a); + this.b = (b instanceof Outlook.Items) ? b : new Outlook.Items(b); + + this.count = function () { + return this.a.count() + this.b.count(); + }; + + this.get = function (idx) { + return null; + }; + + this.forEach = function (fn, maxCount) { + var seen = {}; + var emitted = 0; + + var emit = function (it, idx) { + if (!it) return false; + if (!(it instanceof Outlook.MailItem)) return false; + + var entryId = it.mail.EntryID; + if (!entryId) entryId = String(it.getSubject()) + "|" + String(it.getReceivedTime()); + + if (seen[entryId]) return false; + seen[entryId] = true; + + fn(it, idx); + emitted++; + + if (typeof maxCount === "number" && maxCount > 0 && emitted >= maxCount) return true; + return false; + }; + + var na = this.a.count(); + for (var i = 1; i <= na; i++) { + if (emit(this.a.get(i), i)) return; + } + + var nb = this.b.count(); + for (var j = 1; j <= nb; j++) { + if (emit(this.b.get(j), j)) return; + } + }; +}; + Outlook.Item = function (item) { this.item = item; @@ -519,52 +566,40 @@ Outlook.MailItem = function (mail) { }; Outlook.Search = {}; - Outlook.Search.filters = {}; -// Jet: escape for single-quoted literals Outlook.Search.filters._escape = function (s) { return (s + "").replace(/'/g, "''"); }; -// DASL: escape for single-quoted literals (same rule) Outlook.Search.filters._escapeDASL = function (s) { return (s + "").replace(/'/g, "''"); }; -// 1) Subject contains (Jet, wildcard = *) Outlook.Search.filters.subjectContains = function (keyword) { var k = Outlook.Search.filters._escape(keyword); return "([Subject] Like '*" + k + "*')"; }; -// 2) Sender contains (Jet, wildcard = *) Outlook.Search.filters.senderContains = function (keyword) { var k = Outlook.Search.filters._escape(keyword); return "([SenderEmailAddress] Like '*" + k + "*') OR ([SenderName] Like '*" + k + "*')"; }; -// 3) Sender email equals (Jet) Outlook.Search.filters.senderEmailEquals = function (email) { var e = Outlook.Search.filters._escape(email); return "([SenderEmailAddress] = '" + e + "')"; }; -// 4) Recipient contains (DASL; use @SQL= with % wildcard) Outlook.Search.filters.recipientContains = function (keyword) { + // Legacy name kept for compatibility: this returns DASL now var k = Outlook.Search.filters._escapeDASL(keyword); - - // Note: do NOT wrap property names in [] in DASL. - // Use httpmail display fields for recipient display strings. return '@SQL=' + '"urn:schemas:httpmail:displayto" LIKE \'%' + k + '%\' OR ' + '"urn:schemas:httpmail:displaycc" LIKE \'%' + k + '%\' OR ' + '"urn:schemas:httpmail:displaybcc" LIKE \'%' + k + '%\''; }; -// 5) Sender OR To/CC/BCC contains -// IMPORTANT: cannot mix Jet and DASL in a single Restrict filter. -// Provide TWO filters: Jet (sender) + DASL (recipients), and let caller intersect/union in script. Outlook.Search.filters.senderContains_Jet = function (keyword) { return Outlook.Search.filters.senderContains(keyword); }; @@ -573,15 +608,11 @@ Outlook.Search.filters.recipientContains_DASL = function (keyword) { return Outlook.Search.filters.recipientContains(keyword); }; -// Optional helper: receivedSince in Jet (often OK), but keep separate to avoid mixing with DASL Outlook.Search.filters.receivedSince = function (dateObj) { return "([ReceivedTime] >= '" + dateObj + "')"; }; -// Optional helper: receivedSince in DASL (if you want to apply with other DASL filters) Outlook.Search.filters.receivedSince_DASL = function (dateObj) { - // Use the same date string format you pass elsewhere; Outlook parses locale-dependent strings. - // This uses DAV:date-received. If your environment is picky, keep date restriction in Jet step. var d = Outlook.Search.filters._escapeDASL(dateObj); return '@SQL="DAV:date-received" >= \'' + d + '\''; };