@littlebearapps/outlook-assistant 3.6.0 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/advanced/index.js +5 -5
- package/auth/device-code.js +3 -1
- package/auth/oauth-server.js +4 -4
- package/auth/token-manager.js +2 -2
- package/calendar/list.js +3 -3
- package/categories/index.js +4 -4
- package/contacts/index.js +11 -18
- package/email/conversations.js +19 -17
- package/email/delta.js +1 -1
- package/email/draft.js +6 -8
- package/email/folder-utils.js +1 -1
- package/email/headers.js +10 -10
- package/email/list.js +1 -1
- package/email/mime.js +13 -12
- package/email/search.js +3 -2
- package/email/send.js +1 -1
- package/folder/list.js +1 -1
- package/llms.txt +2 -1
- package/package.json +1 -1
- package/rules/create.js +85 -161
- package/rules/index.js +162 -29
- package/rules/list.js +144 -62
- package/rules/rule-builder.js +284 -0
- package/rules/update.js +213 -0
- package/settings/index.js +10 -22
- package/utils/graph-api.js +1 -1
- package/utils/response-formatter.js +8 -6
- package/utils/safety.js +113 -0
package/rules/list.js
CHANGED
|
@@ -13,13 +13,8 @@ async function handleListRules(args) {
|
|
|
13
13
|
const includeDetails = args.includeDetails === true;
|
|
14
14
|
|
|
15
15
|
try {
|
|
16
|
-
// Get access token
|
|
17
16
|
const accessToken = await ensureAuthenticated();
|
|
18
|
-
|
|
19
|
-
// Get all inbox rules
|
|
20
17
|
const rules = await getInboxRules(accessToken);
|
|
21
|
-
|
|
22
|
-
// Format the rules based on detail level
|
|
23
18
|
const formattedRules = formatRulesList(rules, includeDetails);
|
|
24
19
|
|
|
25
20
|
return {
|
|
@@ -36,7 +31,7 @@ async function handleListRules(args) {
|
|
|
36
31
|
content: [
|
|
37
32
|
{
|
|
38
33
|
type: 'text',
|
|
39
|
-
text: "Authentication required. Please use the '
|
|
34
|
+
text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
|
|
40
35
|
},
|
|
41
36
|
],
|
|
42
37
|
};
|
|
@@ -82,45 +77,43 @@ async function getInboxRules(accessToken) {
|
|
|
82
77
|
*/
|
|
83
78
|
function formatRulesList(rules, includeDetails) {
|
|
84
79
|
if (!rules || rules.length === 0) {
|
|
85
|
-
return
|
|
80
|
+
return 'No inbox rules found.\n\nTip: You can create rules using manage-rules with action=create. Rules are processed in order of their sequence number (lower numbers are processed first).';
|
|
86
81
|
}
|
|
87
82
|
|
|
88
|
-
// Sort rules by sequence to show execution order
|
|
89
83
|
const sortedRules = [...rules].sort((a, b) => {
|
|
90
84
|
return (a.sequence || 9999) - (b.sequence || 9999);
|
|
91
85
|
});
|
|
92
86
|
|
|
93
|
-
// Format rules based on detail level
|
|
94
87
|
if (includeDetails) {
|
|
95
|
-
// Detailed format
|
|
96
88
|
const detailedRules = sortedRules.map((rule) => {
|
|
97
|
-
// Format rule header with sequence
|
|
98
89
|
let ruleText = `[${rule.sequence || 'N/A'}] ${rule.displayName}${rule.isEnabled ? '' : ' (Disabled)'}`;
|
|
99
90
|
|
|
100
|
-
// Format conditions
|
|
101
91
|
const conditions = formatRuleConditions(rule);
|
|
102
92
|
if (conditions) {
|
|
103
93
|
ruleText += `\n Conditions: ${conditions}`;
|
|
104
94
|
}
|
|
105
95
|
|
|
106
|
-
// Format actions
|
|
107
96
|
const actions = formatRuleActions(rule);
|
|
108
97
|
if (actions) {
|
|
109
98
|
ruleText += `\n Actions: ${actions}`;
|
|
110
99
|
}
|
|
111
100
|
|
|
101
|
+
const exceptions = formatRuleExceptions(rule);
|
|
102
|
+
if (exceptions) {
|
|
103
|
+
ruleText += `\n Exceptions: ${exceptions}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
112
106
|
return ruleText;
|
|
113
107
|
});
|
|
114
108
|
|
|
115
109
|
return `Found ${rules.length} inbox rules (sorted by execution order):\n\n${detailedRules.join('\n\n')}\n\nRules are processed in order of their sequence number. You can change rule order using manage-rules with action=reorder.`;
|
|
116
|
-
} else {
|
|
117
|
-
// Simple format
|
|
118
|
-
const simpleRules = sortedRules.map((rule) => {
|
|
119
|
-
return `[${rule.sequence || 'N/A'}] ${rule.displayName}${rule.isEnabled ? '' : ' (Disabled)'}`;
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
return `Found ${rules.length} inbox rules (sorted by execution order):\n\n${simpleRules.join('\n')}\n\nTip: Use 'list-rules with includeDetails=true' to see more information about each rule.`;
|
|
123
110
|
}
|
|
111
|
+
|
|
112
|
+
const simpleRules = sortedRules.map((rule) => {
|
|
113
|
+
return `[${rule.sequence || 'N/A'}] ${rule.displayName}${rule.isEnabled ? '' : ' (Disabled)'}`;
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return `Found ${rules.length} inbox rules (sorted by execution order):\n\n${simpleRules.join('\n')}\n\nTip: Use manage-rules with includeDetails=true to see conditions, actions, and exceptions for each rule.`;
|
|
124
117
|
}
|
|
125
118
|
|
|
126
119
|
/**
|
|
@@ -130,37 +123,76 @@ function formatRulesList(rules, includeDetails) {
|
|
|
130
123
|
*/
|
|
131
124
|
function formatRuleConditions(rule) {
|
|
132
125
|
const conditions = [];
|
|
126
|
+
const c = rule.conditions;
|
|
127
|
+
if (!c) return '';
|
|
133
128
|
|
|
134
|
-
//
|
|
135
|
-
if (
|
|
136
|
-
const senders =
|
|
137
|
-
.map((addr) => addr.emailAddress
|
|
129
|
+
// Recipient-based
|
|
130
|
+
if (c.fromAddresses?.length > 0) {
|
|
131
|
+
const senders = c.fromAddresses
|
|
132
|
+
.map((addr) => addr.emailAddress?.address)
|
|
138
133
|
.join(', ');
|
|
139
134
|
conditions.push(`From: ${senders}`);
|
|
140
135
|
}
|
|
136
|
+
if (c.sentToAddresses?.length > 0) {
|
|
137
|
+
const recipients = c.sentToAddresses
|
|
138
|
+
.map((addr) => addr.emailAddress?.address)
|
|
139
|
+
.join(', ');
|
|
140
|
+
conditions.push(`Sent to: ${recipients}`);
|
|
141
|
+
}
|
|
141
142
|
|
|
142
|
-
//
|
|
143
|
-
if (
|
|
143
|
+
// String collections
|
|
144
|
+
if (c.subjectContains?.length > 0) {
|
|
145
|
+
conditions.push(`Subject contains: "${c.subjectContains.join('", "')}"`);
|
|
146
|
+
}
|
|
147
|
+
if (c.bodyContains?.length > 0) {
|
|
148
|
+
conditions.push(`Body contains: "${c.bodyContains.join('", "')}"`);
|
|
149
|
+
}
|
|
150
|
+
if (c.bodyOrSubjectContains?.length > 0) {
|
|
144
151
|
conditions.push(
|
|
145
|
-
`
|
|
152
|
+
`Body/subject contains: "${c.bodyOrSubjectContains.join('", "')}"`
|
|
146
153
|
);
|
|
147
154
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
155
|
+
if (c.senderContains?.length > 0) {
|
|
156
|
+
conditions.push(`Sender contains: "${c.senderContains.join('", "')}"`);
|
|
157
|
+
}
|
|
158
|
+
if (c.recipientContains?.length > 0) {
|
|
151
159
|
conditions.push(
|
|
152
|
-
`
|
|
160
|
+
`Recipient contains: "${c.recipientContains.join('", "')}"`
|
|
153
161
|
);
|
|
154
162
|
}
|
|
163
|
+
if (c.headerContains?.length > 0) {
|
|
164
|
+
conditions.push(`Header contains: "${c.headerContains.join('", "')}"`);
|
|
165
|
+
}
|
|
155
166
|
|
|
156
|
-
//
|
|
157
|
-
if (
|
|
158
|
-
|
|
167
|
+
// Booleans
|
|
168
|
+
if (c.hasAttachment === true) conditions.push('Has attachment');
|
|
169
|
+
if (c.sentToMe === true) conditions.push('Sent to me');
|
|
170
|
+
if (c.sentOnlyToMe === true) conditions.push('Sent only to me');
|
|
171
|
+
if (c.sentCcMe === true) conditions.push('I am in CC');
|
|
172
|
+
if (c.sentToOrCcMe === true) conditions.push('Sent to me or CC');
|
|
173
|
+
if (c.isAutomaticReply === true) conditions.push('Is automatic reply');
|
|
174
|
+
if (c.isAutomaticForward === true) conditions.push('Is automatic forward');
|
|
175
|
+
if (c.isMeetingRequest === true) conditions.push('Is meeting request');
|
|
176
|
+
if (c.isMeetingResponse === true) conditions.push('Is meeting response');
|
|
177
|
+
if (c.isReadReceipt === true) conditions.push('Is read receipt');
|
|
178
|
+
if (c.isEncrypted === true) conditions.push('Is encrypted');
|
|
179
|
+
if (c.notSentToMe === true) conditions.push('Not sent to me');
|
|
180
|
+
|
|
181
|
+
// Enums
|
|
182
|
+
if (c.importance) conditions.push(`Importance: ${c.importance}`);
|
|
183
|
+
if (c.sensitivity) conditions.push(`Sensitivity: ${c.sensitivity}`);
|
|
184
|
+
if (c.messageActionFlag) conditions.push(`Flag: ${c.messageActionFlag}`);
|
|
185
|
+
|
|
186
|
+
// Categories
|
|
187
|
+
if (c.categories?.length > 0) {
|
|
188
|
+
conditions.push(`Categories: ${c.categories.join(', ')}`);
|
|
159
189
|
}
|
|
160
190
|
|
|
161
|
-
//
|
|
162
|
-
if (
|
|
163
|
-
|
|
191
|
+
// Size range
|
|
192
|
+
if (c.withinSizeRange) {
|
|
193
|
+
const min = c.withinSizeRange.minimumSize || 0;
|
|
194
|
+
const max = c.withinSizeRange.maximumSize || '∞';
|
|
195
|
+
conditions.push(`Size: ${min}–${max} bytes`);
|
|
164
196
|
}
|
|
165
197
|
|
|
166
198
|
return conditions.join('; ');
|
|
@@ -173,43 +205,93 @@ function formatRuleConditions(rule) {
|
|
|
173
205
|
*/
|
|
174
206
|
function formatRuleActions(rule) {
|
|
175
207
|
const actions = [];
|
|
208
|
+
const a = rule.actions;
|
|
209
|
+
if (!a) return '';
|
|
176
210
|
|
|
177
|
-
|
|
178
|
-
if (
|
|
179
|
-
|
|
180
|
-
}
|
|
211
|
+
if (a.moveToFolder) actions.push(`Move to folder: ${a.moveToFolder}`);
|
|
212
|
+
if (a.copyToFolder) actions.push(`Copy to folder: ${a.copyToFolder}`);
|
|
213
|
+
if (a.markAsRead === true) actions.push('Mark as read');
|
|
214
|
+
if (a.markImportance) actions.push(`Mark importance: ${a.markImportance}`);
|
|
181
215
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
// Mark as read
|
|
188
|
-
if (rule.actions?.markAsRead === true) {
|
|
189
|
-
actions.push('Mark as read');
|
|
216
|
+
if (a.forwardTo?.length > 0) {
|
|
217
|
+
const recipients = a.forwardTo
|
|
218
|
+
.map((r) => r.emailAddress?.address)
|
|
219
|
+
.join(', ');
|
|
220
|
+
actions.push(`Forward to: ${recipients}`);
|
|
190
221
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
222
|
+
if (a.forwardAsAttachmentTo?.length > 0) {
|
|
223
|
+
const recipients = a.forwardAsAttachmentTo
|
|
224
|
+
.map((r) => r.emailAddress?.address)
|
|
225
|
+
.join(', ');
|
|
226
|
+
actions.push(`Forward as attachment to: ${recipients}`);
|
|
195
227
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const recipients = rule.actions.forwardTo
|
|
200
|
-
.map((r) => r.emailAddress.address)
|
|
228
|
+
if (a.redirectTo?.length > 0) {
|
|
229
|
+
const recipients = a.redirectTo
|
|
230
|
+
.map((r) => r.emailAddress?.address)
|
|
201
231
|
.join(', ');
|
|
202
|
-
actions.push(`
|
|
232
|
+
actions.push(`Redirect to: ${recipients}`);
|
|
203
233
|
}
|
|
204
234
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
actions.push('Delete');
|
|
235
|
+
if (a.assignCategories?.length > 0) {
|
|
236
|
+
actions.push(`Assign categories: ${a.assignCategories.join(', ')}`);
|
|
208
237
|
}
|
|
238
|
+
if (a.stopProcessingRules === true) actions.push('Stop processing rules');
|
|
239
|
+
if (a.delete === true) actions.push('Delete (move to Deleted Items)');
|
|
240
|
+
if (a.permanentDelete === true) actions.push('Permanently delete');
|
|
209
241
|
|
|
210
242
|
return actions.join('; ');
|
|
211
243
|
}
|
|
212
244
|
|
|
245
|
+
/**
|
|
246
|
+
* Format rule exceptions for display
|
|
247
|
+
* @param {object} rule - Rule object
|
|
248
|
+
* @returns {string} - Formatted exceptions
|
|
249
|
+
*/
|
|
250
|
+
function formatRuleExceptions(rule) {
|
|
251
|
+
const parts = [];
|
|
252
|
+
const e = rule.exceptions;
|
|
253
|
+
if (!e) return '';
|
|
254
|
+
|
|
255
|
+
if (e.fromAddresses?.length > 0) {
|
|
256
|
+
const senders = e.fromAddresses
|
|
257
|
+
.map((addr) => addr.emailAddress?.address)
|
|
258
|
+
.join(', ');
|
|
259
|
+
parts.push(`From: ${senders}`);
|
|
260
|
+
}
|
|
261
|
+
if (e.sentToAddresses?.length > 0) {
|
|
262
|
+
const recipients = e.sentToAddresses
|
|
263
|
+
.map((addr) => addr.emailAddress?.address)
|
|
264
|
+
.join(', ');
|
|
265
|
+
parts.push(`Sent to: ${recipients}`);
|
|
266
|
+
}
|
|
267
|
+
if (e.subjectContains?.length > 0) {
|
|
268
|
+
parts.push(`Subject contains: "${e.subjectContains.join('", "')}"`);
|
|
269
|
+
}
|
|
270
|
+
if (e.bodyContains?.length > 0) {
|
|
271
|
+
parts.push(`Body contains: "${e.bodyContains.join('", "')}"`);
|
|
272
|
+
}
|
|
273
|
+
if (e.bodyOrSubjectContains?.length > 0) {
|
|
274
|
+
parts.push(
|
|
275
|
+
`Body/subject contains: "${e.bodyOrSubjectContains.join('", "')}"`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
if (e.senderContains?.length > 0) {
|
|
279
|
+
parts.push(`Sender contains: "${e.senderContains.join('", "')}"`);
|
|
280
|
+
}
|
|
281
|
+
if (e.recipientContains?.length > 0) {
|
|
282
|
+
parts.push(`Recipient contains: "${e.recipientContains.join('", "')}"`);
|
|
283
|
+
}
|
|
284
|
+
if (e.hasAttachment === true) parts.push('Has attachment');
|
|
285
|
+
if (e.importance) parts.push(`Importance: ${e.importance}`);
|
|
286
|
+
if (e.sensitivity) parts.push(`Sensitivity: ${e.sensitivity}`);
|
|
287
|
+
if (e.sentToMe === true) parts.push('Sent to me');
|
|
288
|
+
if (e.sentOnlyToMe === true) parts.push('Sent only to me');
|
|
289
|
+
if (e.sentCcMe === true) parts.push('I am in CC');
|
|
290
|
+
if (e.isAutomaticReply === true) parts.push('Is automatic reply');
|
|
291
|
+
|
|
292
|
+
return parts.join('; ');
|
|
293
|
+
}
|
|
294
|
+
|
|
213
295
|
module.exports = {
|
|
214
296
|
handleListRules,
|
|
215
297
|
getInboxRules,
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared rule builder utilities for creating and updating mail rules.
|
|
3
|
+
* Converts flat MCP tool parameters into Microsoft Graph API rule objects.
|
|
4
|
+
*/
|
|
5
|
+
const { getFolderIdByName } = require('../email/folder-utils');
|
|
6
|
+
const { checkRecipientAllowlist } = require('../utils/safety');
|
|
7
|
+
|
|
8
|
+
const VALID_IMPORTANCE = ['low', 'normal', 'high'];
|
|
9
|
+
const VALID_SENSITIVITY = ['normal', 'personal', 'private', 'confidential'];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Parse a comma-separated string into a trimmed, non-empty array.
|
|
13
|
+
* @param {string} str - Comma-separated values
|
|
14
|
+
* @returns {string[]}
|
|
15
|
+
*/
|
|
16
|
+
function parseCommaSeparated(str) {
|
|
17
|
+
if (!str) return [];
|
|
18
|
+
return str
|
|
19
|
+
.split(',')
|
|
20
|
+
.map((s) => s.trim())
|
|
21
|
+
.filter((s) => s.length > 0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Convert a comma-separated email string into Graph API recipient objects.
|
|
26
|
+
* @param {string} emailString - Comma-separated email addresses
|
|
27
|
+
* @returns {Array<{emailAddress: {address: string}}>}
|
|
28
|
+
*/
|
|
29
|
+
function formatRecipientObjects(emailString) {
|
|
30
|
+
return parseCommaSeparated(emailString).map((email) => ({
|
|
31
|
+
emailAddress: { address: email },
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Build a Graph API conditions object from flat tool parameters.
|
|
37
|
+
* @param {object} args - Tool arguments
|
|
38
|
+
* @returns {{ conditions: object, warnings: string[] }}
|
|
39
|
+
*/
|
|
40
|
+
function buildConditions(args) {
|
|
41
|
+
const conditions = {};
|
|
42
|
+
const warnings = [];
|
|
43
|
+
|
|
44
|
+
// String collections (comma-separated → array with OR logic)
|
|
45
|
+
if (args.containsSubject) {
|
|
46
|
+
conditions.subjectContains = parseCommaSeparated(args.containsSubject);
|
|
47
|
+
}
|
|
48
|
+
if (args.bodyContains) {
|
|
49
|
+
conditions.bodyContains = parseCommaSeparated(args.bodyContains);
|
|
50
|
+
}
|
|
51
|
+
if (args.bodyOrSubjectContains) {
|
|
52
|
+
conditions.bodyOrSubjectContains = parseCommaSeparated(
|
|
53
|
+
args.bodyOrSubjectContains
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (args.senderContains) {
|
|
57
|
+
conditions.senderContains = parseCommaSeparated(args.senderContains);
|
|
58
|
+
}
|
|
59
|
+
if (args.recipientContains) {
|
|
60
|
+
conditions.recipientContains = parseCommaSeparated(args.recipientContains);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Recipient collections (comma-separated emails → recipient objects)
|
|
64
|
+
if (args.fromAddresses) {
|
|
65
|
+
const recipients = formatRecipientObjects(args.fromAddresses);
|
|
66
|
+
if (recipients.length > 0) {
|
|
67
|
+
conditions.fromAddresses = recipients;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (args.sentToAddresses) {
|
|
71
|
+
const recipients = formatRecipientObjects(args.sentToAddresses);
|
|
72
|
+
if (recipients.length > 0) {
|
|
73
|
+
conditions.sentToAddresses = recipients;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Boolean conditions
|
|
78
|
+
if (args.hasAttachments === true) conditions.hasAttachment = true;
|
|
79
|
+
if (args.sentToMe === true) conditions.sentToMe = true;
|
|
80
|
+
if (args.sentOnlyToMe === true) conditions.sentOnlyToMe = true;
|
|
81
|
+
if (args.sentCcMe === true) conditions.sentCcMe = true;
|
|
82
|
+
if (args.isAutomaticReply === true) conditions.isAutomaticReply = true;
|
|
83
|
+
|
|
84
|
+
// Enum conditions
|
|
85
|
+
if (args.importance) {
|
|
86
|
+
if (!VALID_IMPORTANCE.includes(args.importance)) {
|
|
87
|
+
warnings.push(
|
|
88
|
+
`Invalid importance "${args.importance}". Must be: ${VALID_IMPORTANCE.join(', ')}`
|
|
89
|
+
);
|
|
90
|
+
} else {
|
|
91
|
+
conditions.importance = args.importance;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (args.sensitivity) {
|
|
95
|
+
if (!VALID_SENSITIVITY.includes(args.sensitivity)) {
|
|
96
|
+
warnings.push(
|
|
97
|
+
`Invalid sensitivity "${args.sensitivity}". Must be: ${VALID_SENSITIVITY.join(', ')}`
|
|
98
|
+
);
|
|
99
|
+
} else {
|
|
100
|
+
conditions.sensitivity = args.sensitivity;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { conditions, warnings };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Build a Graph API actions object from flat tool parameters.
|
|
109
|
+
* Async because folder resolution requires API calls.
|
|
110
|
+
* @param {object} args - Tool arguments
|
|
111
|
+
* @param {string} accessToken - Graph API access token
|
|
112
|
+
* @returns {Promise<{ actions: object, warnings: string[] }>}
|
|
113
|
+
*/
|
|
114
|
+
async function buildActions(args, accessToken) {
|
|
115
|
+
const actions = {};
|
|
116
|
+
const warnings = [];
|
|
117
|
+
|
|
118
|
+
// Folder-based actions (name → ID resolution)
|
|
119
|
+
if (args.moveToFolder) {
|
|
120
|
+
const folderId = await getFolderIdByName(accessToken, args.moveToFolder);
|
|
121
|
+
if (!folderId) {
|
|
122
|
+
warnings.push(`Target folder "${args.moveToFolder}" not found.`);
|
|
123
|
+
} else {
|
|
124
|
+
actions.moveToFolder = folderId;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (args.copyToFolder) {
|
|
128
|
+
const folderId = await getFolderIdByName(accessToken, args.copyToFolder);
|
|
129
|
+
if (!folderId) {
|
|
130
|
+
warnings.push(`Copy-to folder "${args.copyToFolder}" not found.`);
|
|
131
|
+
} else {
|
|
132
|
+
actions.copyToFolder = folderId;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Boolean actions
|
|
137
|
+
if (args.markAsRead === true) actions.markAsRead = true;
|
|
138
|
+
if (args.stopProcessingRules === true) actions.stopProcessingRules = true;
|
|
139
|
+
if (args.deleteMessage === true) {
|
|
140
|
+
actions.delete = true;
|
|
141
|
+
warnings.push('This rule will move matching messages to Deleted Items.');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Enum actions
|
|
145
|
+
if (args.markImportance) {
|
|
146
|
+
if (!VALID_IMPORTANCE.includes(args.markImportance)) {
|
|
147
|
+
warnings.push(
|
|
148
|
+
`Invalid markImportance "${args.markImportance}". Must be: ${VALID_IMPORTANCE.join(', ')}`
|
|
149
|
+
);
|
|
150
|
+
} else {
|
|
151
|
+
actions.markImportance = args.markImportance;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Recipient-based actions (comma-separated emails → recipient objects)
|
|
156
|
+
if (args.forwardTo) {
|
|
157
|
+
const recipients = formatRecipientObjects(args.forwardTo);
|
|
158
|
+
if (recipients.length > 0) {
|
|
159
|
+
// Check allowlist if configured
|
|
160
|
+
const allowlistError = checkRecipientAllowlist(recipients);
|
|
161
|
+
if (allowlistError) {
|
|
162
|
+
warnings.push(
|
|
163
|
+
`Forward recipients blocked by allowlist: ${args.forwardTo}`
|
|
164
|
+
);
|
|
165
|
+
} else {
|
|
166
|
+
actions.forwardTo = recipients;
|
|
167
|
+
warnings.push(
|
|
168
|
+
`This rule will forward all matching emails to: ${args.forwardTo}. Verify these addresses are correct.`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (args.redirectTo) {
|
|
174
|
+
const recipients = formatRecipientObjects(args.redirectTo);
|
|
175
|
+
if (recipients.length > 0) {
|
|
176
|
+
const allowlistError = checkRecipientAllowlist(recipients);
|
|
177
|
+
if (allowlistError) {
|
|
178
|
+
warnings.push(
|
|
179
|
+
`Redirect recipients blocked by allowlist: ${args.redirectTo}`
|
|
180
|
+
);
|
|
181
|
+
} else {
|
|
182
|
+
actions.redirectTo = recipients;
|
|
183
|
+
warnings.push(
|
|
184
|
+
`This rule will redirect all matching emails to: ${args.redirectTo}. The original sender will appear as the sender.`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Category actions
|
|
191
|
+
if (args.assignCategories) {
|
|
192
|
+
actions.assignCategories = parseCommaSeparated(args.assignCategories);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return { actions, warnings };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Build a Graph API exceptions object from except* prefixed parameters.
|
|
200
|
+
* @param {object} args - Tool arguments
|
|
201
|
+
* @returns {{ exceptions: object, warnings: string[] }}
|
|
202
|
+
*/
|
|
203
|
+
function buildExceptions(args) {
|
|
204
|
+
const exceptions = {};
|
|
205
|
+
const warnings = [];
|
|
206
|
+
|
|
207
|
+
if (args.exceptFromAddresses) {
|
|
208
|
+
const recipients = formatRecipientObjects(args.exceptFromAddresses);
|
|
209
|
+
if (recipients.length > 0) {
|
|
210
|
+
exceptions.fromAddresses = recipients;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (args.exceptSubjectContains) {
|
|
214
|
+
exceptions.subjectContains = parseCommaSeparated(
|
|
215
|
+
args.exceptSubjectContains
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
if (args.exceptSenderContains) {
|
|
219
|
+
exceptions.senderContains = parseCommaSeparated(args.exceptSenderContains);
|
|
220
|
+
}
|
|
221
|
+
if (args.exceptBodyContains) {
|
|
222
|
+
exceptions.bodyContains = parseCommaSeparated(args.exceptBodyContains);
|
|
223
|
+
}
|
|
224
|
+
if (args.exceptHasAttachments === true) {
|
|
225
|
+
exceptions.hasAttachment = true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return { exceptions, warnings };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Check if any condition was provided in the args.
|
|
233
|
+
* @param {object} args - Tool arguments
|
|
234
|
+
* @returns {boolean}
|
|
235
|
+
*/
|
|
236
|
+
function hasAnyCondition(args) {
|
|
237
|
+
return Boolean(
|
|
238
|
+
args.fromAddresses ||
|
|
239
|
+
args.containsSubject ||
|
|
240
|
+
args.hasAttachments === true ||
|
|
241
|
+
args.bodyContains ||
|
|
242
|
+
args.bodyOrSubjectContains ||
|
|
243
|
+
args.senderContains ||
|
|
244
|
+
args.recipientContains ||
|
|
245
|
+
args.sentToAddresses ||
|
|
246
|
+
args.importance ||
|
|
247
|
+
args.sensitivity ||
|
|
248
|
+
args.sentToMe === true ||
|
|
249
|
+
args.sentOnlyToMe === true ||
|
|
250
|
+
args.sentCcMe === true ||
|
|
251
|
+
args.isAutomaticReply === true
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Check if any action was provided in the args.
|
|
257
|
+
* @param {object} args - Tool arguments
|
|
258
|
+
* @returns {boolean}
|
|
259
|
+
*/
|
|
260
|
+
function hasAnyAction(args) {
|
|
261
|
+
return Boolean(
|
|
262
|
+
args.moveToFolder ||
|
|
263
|
+
args.copyToFolder ||
|
|
264
|
+
args.markAsRead === true ||
|
|
265
|
+
args.markImportance ||
|
|
266
|
+
args.forwardTo ||
|
|
267
|
+
args.redirectTo ||
|
|
268
|
+
args.assignCategories ||
|
|
269
|
+
args.stopProcessingRules === true ||
|
|
270
|
+
args.deleteMessage === true
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
module.exports = {
|
|
275
|
+
VALID_IMPORTANCE,
|
|
276
|
+
VALID_SENSITIVITY,
|
|
277
|
+
parseCommaSeparated,
|
|
278
|
+
formatRecipientObjects,
|
|
279
|
+
buildConditions,
|
|
280
|
+
buildActions,
|
|
281
|
+
buildExceptions,
|
|
282
|
+
hasAnyCondition,
|
|
283
|
+
hasAnyAction,
|
|
284
|
+
};
|