outlook-cli 1.2.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/CLI.md +89 -0
- package/LICENSE +21 -0
- package/QUICKSTART.md +104 -0
- package/README.md +994 -0
- package/auth/index.js +36 -0
- package/auth/oauth-server.js +139 -0
- package/auth/token-manager.js +199 -0
- package/auth/token-storage.js +282 -0
- package/auth/tools.js +127 -0
- package/calendar/accept.js +64 -0
- package/calendar/cancel.js +64 -0
- package/calendar/create.js +69 -0
- package/calendar/decline.js +64 -0
- package/calendar/delete.js +59 -0
- package/calendar/index.js +123 -0
- package/calendar/list.js +77 -0
- package/cli.js +1349 -0
- package/config.js +84 -0
- package/docs/PROJECT-STRUCTURE.md +52 -0
- package/docs/PUBLISHING.md +86 -0
- package/docs/REFERENCE.md +679 -0
- package/email/folder-utils.js +171 -0
- package/email/index.js +157 -0
- package/email/list.js +89 -0
- package/email/mark-as-read.js +101 -0
- package/email/read.js +128 -0
- package/email/search.js +282 -0
- package/email/send.js +120 -0
- package/folder/create.js +124 -0
- package/folder/index.js +78 -0
- package/folder/list.js +264 -0
- package/folder/move.js +163 -0
- package/index.js +136 -0
- package/outlook-auth-server.js +305 -0
- package/package.json +76 -0
- package/rules/create.js +248 -0
- package/rules/index.js +177 -0
- package/rules/list.js +202 -0
- package/tool-registry.js +54 -0
- package/utils/graph-api.js +120 -0
- package/utils/mock-data.js +145 -0
- package/utils/odata-helpers.js +40 -0
package/rules/index.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email rules management module for Outlook MCP server
|
|
3
|
+
*/
|
|
4
|
+
const handleListRules = require('./list');
|
|
5
|
+
const handleCreateRule = require('./create');
|
|
6
|
+
const { ensureAuthenticated } = require('../auth');
|
|
7
|
+
const { callGraphAPI } = require('../utils/graph-api');
|
|
8
|
+
|
|
9
|
+
// Import getInboxRules for the edit sequence tool
|
|
10
|
+
const { getInboxRules } = require('./list');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Edit rule sequence handler
|
|
14
|
+
* @param {object} args - Tool arguments
|
|
15
|
+
* @returns {object} - MCP response
|
|
16
|
+
*/
|
|
17
|
+
async function handleEditRuleSequence(args) {
|
|
18
|
+
const { ruleName, sequence } = args;
|
|
19
|
+
|
|
20
|
+
if (!ruleName) {
|
|
21
|
+
return {
|
|
22
|
+
content: [{
|
|
23
|
+
type: "text",
|
|
24
|
+
text: "Rule name is required. Please specify the exact name of an existing rule."
|
|
25
|
+
}]
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!sequence || isNaN(sequence) || sequence < 1) {
|
|
30
|
+
return {
|
|
31
|
+
content: [{
|
|
32
|
+
type: "text",
|
|
33
|
+
text: "A positive sequence number is required. Lower numbers run first (higher priority)."
|
|
34
|
+
}]
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
// Get access token
|
|
40
|
+
const accessToken = await ensureAuthenticated();
|
|
41
|
+
|
|
42
|
+
// Get all rules
|
|
43
|
+
const rules = await getInboxRules(accessToken);
|
|
44
|
+
|
|
45
|
+
// Find the rule by name
|
|
46
|
+
const rule = rules.find(r => r.displayName === ruleName);
|
|
47
|
+
if (!rule) {
|
|
48
|
+
return {
|
|
49
|
+
content: [{
|
|
50
|
+
type: "text",
|
|
51
|
+
text: `Rule with name "${ruleName}" not found.`
|
|
52
|
+
}]
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Update the rule sequence
|
|
57
|
+
const updateResult = await callGraphAPI(
|
|
58
|
+
accessToken,
|
|
59
|
+
'PATCH',
|
|
60
|
+
`me/mailFolders/inbox/messageRules/${rule.id}`,
|
|
61
|
+
{
|
|
62
|
+
sequence: sequence
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
content: [{
|
|
68
|
+
type: "text",
|
|
69
|
+
text: `Successfully updated the sequence of rule "${ruleName}" to ${sequence}.`
|
|
70
|
+
}]
|
|
71
|
+
};
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (error.message === 'Authentication required') {
|
|
74
|
+
return {
|
|
75
|
+
content: [{
|
|
76
|
+
type: "text",
|
|
77
|
+
text: "Authentication required. Please use the 'authenticate' tool first."
|
|
78
|
+
}]
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
content: [{
|
|
84
|
+
type: "text",
|
|
85
|
+
text: `Error updating rule sequence: ${error.message}`
|
|
86
|
+
}]
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Rules management tool definitions
|
|
92
|
+
const rulesTools = [
|
|
93
|
+
{
|
|
94
|
+
name: "list-rules",
|
|
95
|
+
description: "Lists inbox rules in your Outlook account",
|
|
96
|
+
inputSchema: {
|
|
97
|
+
type: "object",
|
|
98
|
+
properties: {
|
|
99
|
+
includeDetails: {
|
|
100
|
+
type: "boolean",
|
|
101
|
+
description: "Include detailed rule conditions and actions"
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
required: []
|
|
105
|
+
},
|
|
106
|
+
handler: handleListRules
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: "create-rule",
|
|
110
|
+
description: "Creates a new inbox rule",
|
|
111
|
+
inputSchema: {
|
|
112
|
+
type: "object",
|
|
113
|
+
properties: {
|
|
114
|
+
name: {
|
|
115
|
+
type: "string",
|
|
116
|
+
description: "Name of the rule to create"
|
|
117
|
+
},
|
|
118
|
+
fromAddresses: {
|
|
119
|
+
type: "string",
|
|
120
|
+
description: "Comma-separated list of sender email addresses for the rule"
|
|
121
|
+
},
|
|
122
|
+
containsSubject: {
|
|
123
|
+
type: "string",
|
|
124
|
+
description: "Subject text the email must contain"
|
|
125
|
+
},
|
|
126
|
+
hasAttachments: {
|
|
127
|
+
type: "boolean",
|
|
128
|
+
description: "Whether the rule applies to emails with attachments"
|
|
129
|
+
},
|
|
130
|
+
moveToFolder: {
|
|
131
|
+
type: "string",
|
|
132
|
+
description: "Name of the folder to move matching emails to"
|
|
133
|
+
},
|
|
134
|
+
markAsRead: {
|
|
135
|
+
type: "boolean",
|
|
136
|
+
description: "Whether to mark matching emails as read"
|
|
137
|
+
},
|
|
138
|
+
isEnabled: {
|
|
139
|
+
type: "boolean",
|
|
140
|
+
description: "Whether the rule should be enabled after creation (default: true)"
|
|
141
|
+
},
|
|
142
|
+
sequence: {
|
|
143
|
+
type: "number",
|
|
144
|
+
description: "Order in which the rule is executed (lower numbers run first, default: 100)"
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
required: ["name"]
|
|
148
|
+
},
|
|
149
|
+
handler: handleCreateRule
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: "edit-rule-sequence",
|
|
153
|
+
description: "Changes the execution order of an existing inbox rule",
|
|
154
|
+
inputSchema: {
|
|
155
|
+
type: "object",
|
|
156
|
+
properties: {
|
|
157
|
+
ruleName: {
|
|
158
|
+
type: "string",
|
|
159
|
+
description: "Name of the rule to modify"
|
|
160
|
+
},
|
|
161
|
+
sequence: {
|
|
162
|
+
type: "number",
|
|
163
|
+
description: "New sequence value for the rule (lower numbers run first)"
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
required: ["ruleName", "sequence"]
|
|
167
|
+
},
|
|
168
|
+
handler: handleEditRuleSequence
|
|
169
|
+
}
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
module.exports = {
|
|
173
|
+
rulesTools,
|
|
174
|
+
handleListRules,
|
|
175
|
+
handleCreateRule,
|
|
176
|
+
handleEditRuleSequence
|
|
177
|
+
};
|
package/rules/list.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List rules functionality
|
|
3
|
+
*/
|
|
4
|
+
const { callGraphAPI } = require('../utils/graph-api');
|
|
5
|
+
const { ensureAuthenticated } = require('../auth');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* List rules handler
|
|
9
|
+
* @param {object} args - Tool arguments
|
|
10
|
+
* @returns {object} - MCP response
|
|
11
|
+
*/
|
|
12
|
+
async function handleListRules(args) {
|
|
13
|
+
const includeDetails = args.includeDetails === true;
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
// Get access token
|
|
17
|
+
const accessToken = await ensureAuthenticated();
|
|
18
|
+
|
|
19
|
+
// Get all inbox rules
|
|
20
|
+
const rules = await getInboxRules(accessToken);
|
|
21
|
+
|
|
22
|
+
// Format the rules based on detail level
|
|
23
|
+
const formattedRules = formatRulesList(rules, includeDetails);
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
content: [{
|
|
27
|
+
type: "text",
|
|
28
|
+
text: formattedRules
|
|
29
|
+
}]
|
|
30
|
+
};
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (error.message === 'Authentication required') {
|
|
33
|
+
return {
|
|
34
|
+
content: [{
|
|
35
|
+
type: "text",
|
|
36
|
+
text: "Authentication required. Please use the 'authenticate' tool first."
|
|
37
|
+
}]
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
content: [{
|
|
43
|
+
type: "text",
|
|
44
|
+
text: `Error listing rules: ${error.message}`
|
|
45
|
+
}]
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get all inbox rules
|
|
52
|
+
* @param {string} accessToken - Access token
|
|
53
|
+
* @returns {Promise<Array>} - Array of rule objects
|
|
54
|
+
*/
|
|
55
|
+
async function getInboxRules(accessToken) {
|
|
56
|
+
try {
|
|
57
|
+
const response = await callGraphAPI(
|
|
58
|
+
accessToken,
|
|
59
|
+
'GET',
|
|
60
|
+
'me/mailFolders/inbox/messageRules',
|
|
61
|
+
null
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
return response.value || [];
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(`Error getting inbox rules: ${error.message}`);
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Format rules list for display
|
|
73
|
+
* @param {Array} rules - Array of rule objects
|
|
74
|
+
* @param {boolean} includeDetails - Whether to include detailed conditions and actions
|
|
75
|
+
* @returns {string} - Formatted rules list
|
|
76
|
+
*/
|
|
77
|
+
function formatRulesList(rules, includeDetails) {
|
|
78
|
+
if (!rules || rules.length === 0) {
|
|
79
|
+
return "No inbox rules found.\n\nTip: You can create rules using the 'create-rule' tool. Rules are processed in order of their sequence number (lower numbers are processed first).";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Sort rules by sequence to show execution order
|
|
83
|
+
const sortedRules = [...rules].sort((a, b) => {
|
|
84
|
+
return (a.sequence || 9999) - (b.sequence || 9999);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Format rules based on detail level
|
|
88
|
+
if (includeDetails) {
|
|
89
|
+
// Detailed format
|
|
90
|
+
const detailedRules = sortedRules.map((rule, index) => {
|
|
91
|
+
// Format rule header with sequence
|
|
92
|
+
let ruleText = `${index + 1}. ${rule.displayName}${rule.isEnabled ? '' : ' (Disabled)'} - Sequence: ${rule.sequence || 'N/A'}`;
|
|
93
|
+
|
|
94
|
+
// Format conditions
|
|
95
|
+
const conditions = formatRuleConditions(rule);
|
|
96
|
+
if (conditions) {
|
|
97
|
+
ruleText += `\n Conditions: ${conditions}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Format actions
|
|
101
|
+
const actions = formatRuleActions(rule);
|
|
102
|
+
if (actions) {
|
|
103
|
+
ruleText += `\n Actions: ${actions}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return ruleText;
|
|
107
|
+
});
|
|
108
|
+
|
|
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 the 'edit-rule-sequence' tool.`;
|
|
110
|
+
} else {
|
|
111
|
+
// Simple format
|
|
112
|
+
const simpleRules = sortedRules.map((rule, index) => {
|
|
113
|
+
return `${index + 1}. ${rule.displayName}${rule.isEnabled ? '' : ' (Disabled)'} - Sequence: ${rule.sequence || 'N/A'}`;
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
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.`;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Format rule conditions for display
|
|
122
|
+
* @param {object} rule - Rule object
|
|
123
|
+
* @returns {string} - Formatted conditions
|
|
124
|
+
*/
|
|
125
|
+
function formatRuleConditions(rule) {
|
|
126
|
+
const conditions = [];
|
|
127
|
+
|
|
128
|
+
// From addresses
|
|
129
|
+
if (rule.conditions?.fromAddresses?.length > 0) {
|
|
130
|
+
const senders = rule.conditions.fromAddresses.map(addr => addr.emailAddress.address).join(', ');
|
|
131
|
+
conditions.push(`From: ${senders}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Subject contains
|
|
135
|
+
if (rule.conditions?.subjectContains?.length > 0) {
|
|
136
|
+
conditions.push(`Subject contains: "${rule.conditions.subjectContains.join(', ')}"`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Contains body text
|
|
140
|
+
if (rule.conditions?.bodyContains?.length > 0) {
|
|
141
|
+
conditions.push(`Body contains: "${rule.conditions.bodyContains.join(', ')}"`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Has attachment
|
|
145
|
+
if (rule.conditions?.hasAttachment === true) {
|
|
146
|
+
conditions.push('Has attachment');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Importance
|
|
150
|
+
if (rule.conditions?.importance) {
|
|
151
|
+
conditions.push(`Importance: ${rule.conditions.importance}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return conditions.join('; ');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Format rule actions for display
|
|
159
|
+
* @param {object} rule - Rule object
|
|
160
|
+
* @returns {string} - Formatted actions
|
|
161
|
+
*/
|
|
162
|
+
function formatRuleActions(rule) {
|
|
163
|
+
const actions = [];
|
|
164
|
+
|
|
165
|
+
// Move to folder
|
|
166
|
+
if (rule.actions?.moveToFolder) {
|
|
167
|
+
actions.push(`Move to folder: ${rule.actions.moveToFolder}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Copy to folder
|
|
171
|
+
if (rule.actions?.copyToFolder) {
|
|
172
|
+
actions.push(`Copy to folder: ${rule.actions.copyToFolder}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Mark as read
|
|
176
|
+
if (rule.actions?.markAsRead === true) {
|
|
177
|
+
actions.push('Mark as read');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Mark importance
|
|
181
|
+
if (rule.actions?.markImportance) {
|
|
182
|
+
actions.push(`Mark importance: ${rule.actions.markImportance}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Forward
|
|
186
|
+
if (rule.actions?.forwardTo?.length > 0) {
|
|
187
|
+
const recipients = rule.actions.forwardTo.map(r => r.emailAddress.address).join(', ');
|
|
188
|
+
actions.push(`Forward to: ${recipients}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Delete
|
|
192
|
+
if (rule.actions?.delete === true) {
|
|
193
|
+
actions.push('Delete');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return actions.join('; ');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
module.exports = {
|
|
200
|
+
handleListRules,
|
|
201
|
+
getInboxRules
|
|
202
|
+
};
|
package/tool-registry.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared tool registry used by both the MCP transport and local CLI.
|
|
3
|
+
*/
|
|
4
|
+
const { authTools } = require('./auth');
|
|
5
|
+
const { calendarTools } = require('./calendar');
|
|
6
|
+
const { emailTools } = require('./email');
|
|
7
|
+
const { folderTools } = require('./folder');
|
|
8
|
+
const { rulesTools } = require('./rules');
|
|
9
|
+
|
|
10
|
+
const TOOLS = Object.freeze([
|
|
11
|
+
...authTools,
|
|
12
|
+
...calendarTools,
|
|
13
|
+
...emailTools,
|
|
14
|
+
...folderTools,
|
|
15
|
+
...rulesTools
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
const TOOL_MAP = new Map(TOOLS.map((tool) => [tool.name, tool]));
|
|
19
|
+
|
|
20
|
+
function getTools() {
|
|
21
|
+
return TOOLS;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function listTools() {
|
|
25
|
+
return TOOLS.map((tool) => ({
|
|
26
|
+
name: tool.name,
|
|
27
|
+
description: tool.description,
|
|
28
|
+
inputSchema: tool.inputSchema
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getTool(name) {
|
|
33
|
+
return TOOL_MAP.get(name);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function invokeTool(name, args = {}) {
|
|
37
|
+
const tool = getTool(name);
|
|
38
|
+
|
|
39
|
+
if (!tool || typeof tool.handler !== 'function') {
|
|
40
|
+
const error = new Error(`Tool not found: ${name}`);
|
|
41
|
+
error.code = 'TOOL_NOT_FOUND';
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return tool.handler(args);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = {
|
|
49
|
+
TOOLS,
|
|
50
|
+
getTools,
|
|
51
|
+
listTools,
|
|
52
|
+
getTool,
|
|
53
|
+
invokeTool
|
|
54
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Microsoft Graph API helper functions
|
|
3
|
+
*/
|
|
4
|
+
const https = require('https');
|
|
5
|
+
const config = require('../config');
|
|
6
|
+
const mockData = require('./mock-data');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Makes a request to the Microsoft Graph API
|
|
10
|
+
* @param {string} accessToken - The access token for authentication
|
|
11
|
+
* @param {string} method - HTTP method (GET, POST, etc.)
|
|
12
|
+
* @param {string} path - API endpoint path
|
|
13
|
+
* @param {object} data - Data to send for POST/PUT requests
|
|
14
|
+
* @param {object} queryParams - Query parameters
|
|
15
|
+
* @returns {Promise<object>} - The API response
|
|
16
|
+
*/
|
|
17
|
+
async function callGraphAPI(accessToken, method, path, data = null, queryParams = {}) {
|
|
18
|
+
// For test tokens, we'll simulate the API call
|
|
19
|
+
if (config.USE_TEST_MODE && accessToken.startsWith('test_access_token_')) {
|
|
20
|
+
console.error(`TEST MODE: Simulating ${method} ${path} API call`);
|
|
21
|
+
return mockData.simulateGraphAPIResponse(method, path, data, queryParams);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
console.error(`Making real API call: ${method} ${path}`);
|
|
26
|
+
|
|
27
|
+
// Encode path segments properly
|
|
28
|
+
const encodedPath = path.split('/')
|
|
29
|
+
.map(segment => encodeURIComponent(segment))
|
|
30
|
+
.join('/');
|
|
31
|
+
|
|
32
|
+
// Build query string from parameters with special handling for OData filters
|
|
33
|
+
let queryString = '';
|
|
34
|
+
if (Object.keys(queryParams).length > 0) {
|
|
35
|
+
// Handle $filter parameter specially to ensure proper URI encoding
|
|
36
|
+
const filter = queryParams.$filter;
|
|
37
|
+
if (filter) {
|
|
38
|
+
delete queryParams.$filter; // Remove from regular params
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Build query string with proper encoding for regular params
|
|
42
|
+
const params = new URLSearchParams();
|
|
43
|
+
for (const [key, value] of Object.entries(queryParams)) {
|
|
44
|
+
params.append(key, value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
queryString = params.toString();
|
|
48
|
+
|
|
49
|
+
// Add filter parameter separately with proper encoding
|
|
50
|
+
if (filter) {
|
|
51
|
+
if (queryString) {
|
|
52
|
+
queryString += `&$filter=${encodeURIComponent(filter)}`;
|
|
53
|
+
} else {
|
|
54
|
+
queryString = `$filter=${encodeURIComponent(filter)}`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (queryString) {
|
|
59
|
+
queryString = '?' + queryString;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.error(`Query string: ${queryString}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const url = `${config.GRAPH_API_ENDPOINT}${encodedPath}${queryString}`;
|
|
66
|
+
console.error(`Full URL: ${url}`);
|
|
67
|
+
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const options = {
|
|
70
|
+
method: method,
|
|
71
|
+
headers: {
|
|
72
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
73
|
+
'Content-Type': 'application/json'
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const req = https.request(url, options, (res) => {
|
|
78
|
+
let responseData = '';
|
|
79
|
+
|
|
80
|
+
res.on('data', (chunk) => {
|
|
81
|
+
responseData += chunk;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
res.on('end', () => {
|
|
85
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
86
|
+
try {
|
|
87
|
+
responseData = responseData ? responseData : '{}';
|
|
88
|
+
const jsonResponse = JSON.parse(responseData);
|
|
89
|
+
resolve(jsonResponse);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
reject(new Error(`Error parsing API response: ${error.message}`));
|
|
92
|
+
}
|
|
93
|
+
} else if (res.statusCode === 401) {
|
|
94
|
+
// Token expired or invalid
|
|
95
|
+
reject(new Error('UNAUTHORIZED'));
|
|
96
|
+
} else {
|
|
97
|
+
reject(new Error(`API call failed with status ${res.statusCode}: ${responseData}`));
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
req.on('error', (error) => {
|
|
103
|
+
reject(new Error(`Network error during API call: ${error.message}`));
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (data && (method === 'POST' || method === 'PATCH' || method === 'PUT')) {
|
|
107
|
+
req.write(JSON.stringify(data));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
req.end();
|
|
111
|
+
});
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error('Error calling Graph API:', error);
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = {
|
|
119
|
+
callGraphAPI
|
|
120
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock data functions for test mode
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Simulates Microsoft Graph API responses for testing
|
|
7
|
+
* @param {string} method - HTTP method
|
|
8
|
+
* @param {string} path - API path
|
|
9
|
+
* @param {object} data - Request data
|
|
10
|
+
* @param {object} queryParams - Query parameters
|
|
11
|
+
* @returns {object} - Simulated API response
|
|
12
|
+
*/
|
|
13
|
+
function simulateGraphAPIResponse(method, path, data, queryParams) {
|
|
14
|
+
console.error(`Simulating response for: ${method} ${path}`);
|
|
15
|
+
|
|
16
|
+
if (method === 'GET') {
|
|
17
|
+
if (path.includes('messages') && !path.includes('sendMail')) {
|
|
18
|
+
// Simulate a successful email list/search response
|
|
19
|
+
if (path.includes('/messages/')) {
|
|
20
|
+
// Single email response
|
|
21
|
+
return {
|
|
22
|
+
id: "simulated-email-id",
|
|
23
|
+
subject: "Simulated Email Subject",
|
|
24
|
+
from: {
|
|
25
|
+
emailAddress: {
|
|
26
|
+
name: "Simulated Sender",
|
|
27
|
+
address: "sender@example.com"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
toRecipients: [{
|
|
31
|
+
emailAddress: {
|
|
32
|
+
name: "Recipient Name",
|
|
33
|
+
address: "recipient@example.com"
|
|
34
|
+
}
|
|
35
|
+
}],
|
|
36
|
+
ccRecipients: [],
|
|
37
|
+
bccRecipients: [],
|
|
38
|
+
receivedDateTime: new Date().toISOString(),
|
|
39
|
+
bodyPreview: "This is a simulated email preview...",
|
|
40
|
+
body: {
|
|
41
|
+
contentType: "text",
|
|
42
|
+
content: "This is the full content of the simulated email. Since we can't connect to the real Microsoft Graph API, we're returning this placeholder content instead."
|
|
43
|
+
},
|
|
44
|
+
hasAttachments: false,
|
|
45
|
+
importance: "normal",
|
|
46
|
+
isRead: false,
|
|
47
|
+
internetMessageHeaders: []
|
|
48
|
+
};
|
|
49
|
+
} else {
|
|
50
|
+
// Email list response
|
|
51
|
+
return {
|
|
52
|
+
value: [
|
|
53
|
+
{
|
|
54
|
+
id: "simulated-email-1",
|
|
55
|
+
subject: "Important Meeting Tomorrow",
|
|
56
|
+
from: {
|
|
57
|
+
emailAddress: {
|
|
58
|
+
name: "John Doe",
|
|
59
|
+
address: "john@example.com"
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
toRecipients: [{
|
|
63
|
+
emailAddress: {
|
|
64
|
+
name: "You",
|
|
65
|
+
address: "you@example.com"
|
|
66
|
+
}
|
|
67
|
+
}],
|
|
68
|
+
ccRecipients: [],
|
|
69
|
+
receivedDateTime: new Date().toISOString(),
|
|
70
|
+
bodyPreview: "Let's discuss the project status...",
|
|
71
|
+
hasAttachments: false,
|
|
72
|
+
importance: "high",
|
|
73
|
+
isRead: false
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: "simulated-email-2",
|
|
77
|
+
subject: "Weekly Report",
|
|
78
|
+
from: {
|
|
79
|
+
emailAddress: {
|
|
80
|
+
name: "Jane Smith",
|
|
81
|
+
address: "jane@example.com"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
toRecipients: [{
|
|
85
|
+
emailAddress: {
|
|
86
|
+
name: "You",
|
|
87
|
+
address: "you@example.com"
|
|
88
|
+
}
|
|
89
|
+
}],
|
|
90
|
+
ccRecipients: [],
|
|
91
|
+
receivedDateTime: new Date(Date.now() - 86400000).toISOString(), // Yesterday
|
|
92
|
+
bodyPreview: "Please find attached the weekly report...",
|
|
93
|
+
hasAttachments: true,
|
|
94
|
+
importance: "normal",
|
|
95
|
+
isRead: true
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: "simulated-email-3",
|
|
99
|
+
subject: "Question about the project",
|
|
100
|
+
from: {
|
|
101
|
+
emailAddress: {
|
|
102
|
+
name: "Bob Johnson",
|
|
103
|
+
address: "bob@example.com"
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
toRecipients: [{
|
|
107
|
+
emailAddress: {
|
|
108
|
+
name: "You",
|
|
109
|
+
address: "you@example.com"
|
|
110
|
+
}
|
|
111
|
+
}],
|
|
112
|
+
ccRecipients: [],
|
|
113
|
+
receivedDateTime: new Date(Date.now() - 172800000).toISOString(), // 2 days ago
|
|
114
|
+
bodyPreview: "I had a question about the timeline...",
|
|
115
|
+
hasAttachments: false,
|
|
116
|
+
importance: "normal",
|
|
117
|
+
isRead: false
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
} else if (path.includes('mailFolders')) {
|
|
123
|
+
// Simulate a mail folders response
|
|
124
|
+
return {
|
|
125
|
+
value: [
|
|
126
|
+
{ id: "inbox", displayName: "Inbox" },
|
|
127
|
+
{ id: "drafts", displayName: "Drafts" },
|
|
128
|
+
{ id: "sentItems", displayName: "Sent Items" },
|
|
129
|
+
{ id: "deleteditems", displayName: "Deleted Items" }
|
|
130
|
+
]
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
} else if (method === 'POST' && path.includes('sendMail')) {
|
|
134
|
+
// Simulate a successful email send
|
|
135
|
+
return {};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// If we get here, we don't have a simulation for this endpoint
|
|
139
|
+
console.error(`No simulation available for: ${method} ${path}`);
|
|
140
|
+
return {};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = {
|
|
144
|
+
simulateGraphAPIResponse
|
|
145
|
+
};
|