m365-cli 0.1.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 +683 -0
- package/bin/m365.js +489 -0
- package/config/default.json +18 -0
- package/package.json +36 -0
- package/src/auth/device-flow.js +154 -0
- package/src/auth/token-manager.js +237 -0
- package/src/commands/calendar.js +279 -0
- package/src/commands/mail.js +353 -0
- package/src/commands/onedrive.js +423 -0
- package/src/commands/sharepoint.js +312 -0
- package/src/graph/client.js +875 -0
- package/src/utils/config.js +60 -0
- package/src/utils/error.js +114 -0
- package/src/utils/output.js +850 -0
- package/src/utils/trusted-senders.js +190 -0
|
@@ -0,0 +1,850 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output formatting utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Format date to readable string
|
|
7
|
+
*/
|
|
8
|
+
function formatDate(dateString) {
|
|
9
|
+
if (!dateString) return 'N/A';
|
|
10
|
+
|
|
11
|
+
const date = new Date(dateString);
|
|
12
|
+
const year = date.getFullYear();
|
|
13
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
14
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
15
|
+
const hours = String(date.getHours()).padStart(2, '0');
|
|
16
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
17
|
+
|
|
18
|
+
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Truncate string to max length
|
|
23
|
+
*/
|
|
24
|
+
function truncate(str, maxLength = 60) {
|
|
25
|
+
if (!str) return '';
|
|
26
|
+
if (str.length <= maxLength) return str;
|
|
27
|
+
return str.slice(0, maxLength - 3) + '...';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Strip HTML tags from string
|
|
32
|
+
*/
|
|
33
|
+
function stripHtml(html) {
|
|
34
|
+
if (!html) return '';
|
|
35
|
+
return html
|
|
36
|
+
.replace(/<style[^>]*>.*?<\/style>/gi, '')
|
|
37
|
+
.replace(/<script[^>]*>.*?<\/script>/gi, '')
|
|
38
|
+
.replace(/<[^>]+>/g, '')
|
|
39
|
+
.replace(/ /g, ' ')
|
|
40
|
+
.replace(/&/g, '&')
|
|
41
|
+
.replace(/</g, '<')
|
|
42
|
+
.replace(/>/g, '>')
|
|
43
|
+
.replace(/"/g, '"')
|
|
44
|
+
.trim();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Output mail list in text format
|
|
49
|
+
*/
|
|
50
|
+
export function outputMailList(mails, options = {}) {
|
|
51
|
+
const { json = false, top = 10 } = options;
|
|
52
|
+
|
|
53
|
+
if (json) {
|
|
54
|
+
// Fix sender display for JSON output
|
|
55
|
+
const enrichedMails = mails.map(mail => {
|
|
56
|
+
const fromAddress = mail.from?.emailAddress?.address || '';
|
|
57
|
+
const fromName = mail.from?.emailAddress?.name || '';
|
|
58
|
+
const displayFrom = fromAddress.includes('@') ? fromAddress : (fromName || 'Unknown');
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
...mail,
|
|
62
|
+
from: {
|
|
63
|
+
...mail.from,
|
|
64
|
+
emailAddress: {
|
|
65
|
+
...mail.from?.emailAddress,
|
|
66
|
+
displayAddress: displayFrom,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
console.log(JSON.stringify(enrichedMails, null, 2));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!mails || mails.length === 0) {
|
|
76
|
+
console.log('📭 No emails found.');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log(`📧 Mail List (top ${Math.min(mails.length, top)})`);
|
|
81
|
+
console.log('━'.repeat(60));
|
|
82
|
+
|
|
83
|
+
mails.forEach((mail, index) => {
|
|
84
|
+
const status = mail.isRead ? '✅' : '📩';
|
|
85
|
+
// Prefer email address, but fall back to name if address is Exchange DN format
|
|
86
|
+
const fromAddress = mail.from?.emailAddress?.address || '';
|
|
87
|
+
const fromName = mail.from?.emailAddress?.name || '';
|
|
88
|
+
const from = fromAddress.includes('@') ? fromAddress : (fromName || 'Unknown');
|
|
89
|
+
const subject = truncate(mail.subject || '(No subject)', 50);
|
|
90
|
+
const date = formatDate(mail.receivedDateTime);
|
|
91
|
+
|
|
92
|
+
// Add warning for untrusted senders
|
|
93
|
+
const trustIndicator = mail.isTrusted === false ? ' ⚠️' : '';
|
|
94
|
+
|
|
95
|
+
console.log(`[${index + 1}] ${status} ${subject}${trustIndicator}`);
|
|
96
|
+
console.log(` From: ${from}`);
|
|
97
|
+
console.log(` Date: ${date}`);
|
|
98
|
+
console.log(` ID: ${mail.id?.slice(0, 20)}...`);
|
|
99
|
+
console.log('');
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Output mail detail in text format
|
|
105
|
+
*/
|
|
106
|
+
export function outputMailDetail(mail, options = {}) {
|
|
107
|
+
const { json = false } = options;
|
|
108
|
+
|
|
109
|
+
if (json) {
|
|
110
|
+
console.log(JSON.stringify(mail, null, 2));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
console.log('━'.repeat(60));
|
|
115
|
+
console.log(`📧 ${mail.subject || '(No subject)'}`);
|
|
116
|
+
console.log('━'.repeat(60));
|
|
117
|
+
// Prefer email address, but fall back to name if address is Exchange DN format
|
|
118
|
+
const fromAddress = mail.from?.emailAddress?.address || '';
|
|
119
|
+
const fromName = mail.from?.emailAddress?.name || '';
|
|
120
|
+
const from = fromAddress.includes('@') ? fromAddress : (fromName || 'Unknown');
|
|
121
|
+
console.log(`From: ${from}`);
|
|
122
|
+
console.log(`To: ${mail.toRecipients?.map(r => r.emailAddress?.address).join(', ') || 'Unknown'}`);
|
|
123
|
+
console.log(`Date: ${formatDate(mail.receivedDateTime)}`);
|
|
124
|
+
console.log(`Status: ${mail.isRead ? '✅ Read' : '📩 Unread'}`);
|
|
125
|
+
|
|
126
|
+
// Show trust status
|
|
127
|
+
if (mail.isTrusted === false) {
|
|
128
|
+
console.log(`⚠️ Sender not in whitelist - content filtered`);
|
|
129
|
+
}
|
|
130
|
+
if (mail.bodyFiltered) {
|
|
131
|
+
console.log(`💡 Use --force to view full content`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
console.log('━'.repeat(60));
|
|
135
|
+
console.log('');
|
|
136
|
+
|
|
137
|
+
// Body content
|
|
138
|
+
let body = mail.body?.content || '';
|
|
139
|
+
if (mail.body?.contentType === 'html') {
|
|
140
|
+
body = stripHtml(body);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Limit body length for display
|
|
144
|
+
const maxBodyLength = 5000;
|
|
145
|
+
if (body.length > maxBodyLength) {
|
|
146
|
+
body = body.slice(0, maxBodyLength) + '\n\n... (truncated)';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
console.log(body);
|
|
150
|
+
console.log('');
|
|
151
|
+
|
|
152
|
+
// Attachments
|
|
153
|
+
if (mail.hasAttachments && mail.attachments) {
|
|
154
|
+
console.log('━'.repeat(60));
|
|
155
|
+
console.log(`📎 Attachments (${mail.attachments.length})`);
|
|
156
|
+
mail.attachments.forEach(att => {
|
|
157
|
+
const size = att.size ? `(${(att.size / 1024).toFixed(1)} KB)` : '';
|
|
158
|
+
console.log(` • ${att.name} ${size}`);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
console.log('━'.repeat(60));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Output send result
|
|
167
|
+
*/
|
|
168
|
+
export function outputSendResult(result, options = {}) {
|
|
169
|
+
const { json = false } = options;
|
|
170
|
+
|
|
171
|
+
if (json) {
|
|
172
|
+
console.log(JSON.stringify(result, null, 2));
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
console.log('✅ Email sent successfully');
|
|
177
|
+
console.log(` To: ${result.to}`);
|
|
178
|
+
|
|
179
|
+
if (result.cc) {
|
|
180
|
+
console.log(` CC: ${result.cc}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (result.bccCount !== undefined && result.bccCount > 0) {
|
|
184
|
+
console.log(` BCC: ${result.bccCount} recipient(s)`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
console.log(` Subject: ${result.subject}`);
|
|
188
|
+
|
|
189
|
+
if (result.attachments > 0) {
|
|
190
|
+
console.log(` Attachments: ${result.attachments}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Output attachment list
|
|
196
|
+
*/
|
|
197
|
+
export function outputAttachmentList(attachments, options = {}) {
|
|
198
|
+
const { json = false, messageId = '' } = options;
|
|
199
|
+
|
|
200
|
+
if (json) {
|
|
201
|
+
console.log(JSON.stringify(attachments, null, 2));
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (!attachments || attachments.length === 0) {
|
|
206
|
+
console.log('📎 No attachments found.');
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
console.log(`📎 Attachments (${attachments.length})`);
|
|
211
|
+
console.log('━'.repeat(60));
|
|
212
|
+
|
|
213
|
+
attachments.forEach((att, index) => {
|
|
214
|
+
const size = att.size ? formatFileSize(att.size) : 'Unknown size';
|
|
215
|
+
const type = att.contentType || 'Unknown type';
|
|
216
|
+
|
|
217
|
+
console.log(`[${index + 1}] 📄 ${att.name}`);
|
|
218
|
+
console.log(` Size: ${size}`);
|
|
219
|
+
console.log(` Type: ${type}`);
|
|
220
|
+
console.log(` ID: ${att.id}`);
|
|
221
|
+
console.log('');
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Output attachment download result
|
|
227
|
+
*/
|
|
228
|
+
export function outputAttachmentDownload(result, options = {}) {
|
|
229
|
+
const { json = false } = options;
|
|
230
|
+
|
|
231
|
+
if (json) {
|
|
232
|
+
console.log(JSON.stringify(result, null, 2));
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
console.log('✅ Attachment downloaded successfully');
|
|
237
|
+
console.log(` File: ${result.name}`);
|
|
238
|
+
console.log(` Saved to: ${result.path}`);
|
|
239
|
+
console.log(` Size: ${formatFileSize(result.size)}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Output generic success message
|
|
244
|
+
*/
|
|
245
|
+
export function outputSuccess(message, options = {}) {
|
|
246
|
+
const { json = false } = options;
|
|
247
|
+
|
|
248
|
+
if (json) {
|
|
249
|
+
console.log(JSON.stringify({ success: true, message }, null, 2));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
console.log(`✅ ${message}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Output calendar list in text format
|
|
258
|
+
*/
|
|
259
|
+
export function outputCalendarList(events, options = {}) {
|
|
260
|
+
const { json = false, days = 7 } = options;
|
|
261
|
+
|
|
262
|
+
if (json) {
|
|
263
|
+
console.log(JSON.stringify(events, null, 2));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!events || events.length === 0) {
|
|
268
|
+
console.log(`📅 No events in the next ${days} days.`);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
console.log(`📅 Calendar Events (next ${days} days)`);
|
|
273
|
+
console.log('━'.repeat(60));
|
|
274
|
+
|
|
275
|
+
events.forEach((event, index) => {
|
|
276
|
+
const icon = event.isAllDay ? '📆' : '🕐';
|
|
277
|
+
const subject = truncate(event.subject || '(No title)', 50);
|
|
278
|
+
const location = event.location?.displayName || '';
|
|
279
|
+
|
|
280
|
+
let timeStr = '';
|
|
281
|
+
if (event.isAllDay) {
|
|
282
|
+
const startDate = new Date(event.start.dateTime);
|
|
283
|
+
timeStr = `${startDate.toLocaleDateString('en-CA')} (All day)`;
|
|
284
|
+
} else {
|
|
285
|
+
const start = formatDate(event.start.dateTime);
|
|
286
|
+
const end = formatDate(event.end.dateTime);
|
|
287
|
+
timeStr = `${start} → ${end}`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
console.log(`[${index + 1}] ${icon} ${subject}`);
|
|
291
|
+
console.log(` Time: ${timeStr}`);
|
|
292
|
+
if (location) {
|
|
293
|
+
console.log(` Location: ${location}`);
|
|
294
|
+
}
|
|
295
|
+
console.log(` ID: ${event.id?.slice(0, 20)}...`);
|
|
296
|
+
console.log('');
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Output calendar event detail in text format
|
|
302
|
+
*/
|
|
303
|
+
export function outputCalendarDetail(event, options = {}) {
|
|
304
|
+
const { json = false } = options;
|
|
305
|
+
|
|
306
|
+
if (json) {
|
|
307
|
+
console.log(JSON.stringify(event, null, 2));
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
console.log('━'.repeat(60));
|
|
312
|
+
console.log(`📅 ${event.subject || '(No title)'}`);
|
|
313
|
+
console.log('━'.repeat(60));
|
|
314
|
+
|
|
315
|
+
if (event.isAllDay) {
|
|
316
|
+
const startDate = new Date(event.start.dateTime);
|
|
317
|
+
console.log(`When: ${startDate.toLocaleDateString('en-CA')} (All day)`);
|
|
318
|
+
} else {
|
|
319
|
+
console.log(`Start: ${formatDate(event.start.dateTime)}`);
|
|
320
|
+
console.log(`End: ${formatDate(event.end.dateTime)}`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (event.location?.displayName) {
|
|
324
|
+
console.log(`Location: ${event.location.displayName}`);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (event.organizer?.emailAddress) {
|
|
328
|
+
console.log(`Organizer: ${event.organizer.emailAddress.name || event.organizer.emailAddress.address}`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (event.attendees && event.attendees.length > 0) {
|
|
332
|
+
console.log(`Attendees (${event.attendees.length}):`);
|
|
333
|
+
event.attendees.forEach(att => {
|
|
334
|
+
const name = att.emailAddress.name || att.emailAddress.address;
|
|
335
|
+
const status = att.status?.response || 'none';
|
|
336
|
+
console.log(` • ${name} (${status})`);
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
console.log('━'.repeat(60));
|
|
341
|
+
|
|
342
|
+
if (event.body?.content) {
|
|
343
|
+
let body = event.body.content;
|
|
344
|
+
if (event.body.contentType === 'html') {
|
|
345
|
+
body = stripHtml(body);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (body.trim()) {
|
|
349
|
+
console.log('');
|
|
350
|
+
console.log(body.trim());
|
|
351
|
+
console.log('');
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
console.log('━'.repeat(60));
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Output calendar operation result
|
|
360
|
+
*/
|
|
361
|
+
export function outputCalendarResult(result, options = {}) {
|
|
362
|
+
const { json = false } = options;
|
|
363
|
+
|
|
364
|
+
if (json) {
|
|
365
|
+
console.log(JSON.stringify(result, null, 2));
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const statusEmoji = {
|
|
370
|
+
'created': '✅',
|
|
371
|
+
'updated': '✏️',
|
|
372
|
+
'deleted': '🗑️',
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
const emoji = statusEmoji[result.status] || '✅';
|
|
376
|
+
const action = result.status.charAt(0).toUpperCase() + result.status.slice(1);
|
|
377
|
+
|
|
378
|
+
console.log(`${emoji} Event ${action}`);
|
|
379
|
+
|
|
380
|
+
if (result.subject) {
|
|
381
|
+
console.log(` Subject: ${result.subject}`);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (result.start && result.end) {
|
|
385
|
+
console.log(` Time: ${formatDate(result.start)} → ${formatDate(result.end)}`);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (result.id) {
|
|
389
|
+
console.log(` ID: ${result.id.slice(0, 40)}...`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Format file size to human-readable format
|
|
395
|
+
*/
|
|
396
|
+
function formatFileSize(bytes) {
|
|
397
|
+
if (!bytes || bytes === 0) return '0 B';
|
|
398
|
+
|
|
399
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
400
|
+
let size = bytes;
|
|
401
|
+
let unitIndex = 0;
|
|
402
|
+
|
|
403
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
404
|
+
size /= 1024;
|
|
405
|
+
unitIndex++;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// For small files, show integer; for larger ones, show 1 decimal
|
|
409
|
+
if (unitIndex === 0) {
|
|
410
|
+
return `${size} ${units[unitIndex]}`;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Output OneDrive file list in text format
|
|
418
|
+
*/
|
|
419
|
+
export function outputOneDriveList(items, options = {}) {
|
|
420
|
+
const { json = false, path = '' } = options;
|
|
421
|
+
|
|
422
|
+
if (json) {
|
|
423
|
+
// Ensure JSON output includes type field
|
|
424
|
+
const enrichedItems = items.map(item => ({
|
|
425
|
+
...item,
|
|
426
|
+
type: item.folder ? 'folder' : 'file'
|
|
427
|
+
}));
|
|
428
|
+
console.log(JSON.stringify(enrichedItems, null, 2));
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (!items || items.length === 0) {
|
|
433
|
+
console.log(`📁 No items found${path ? ` in "${path}"` : ''}.`);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const displayPath = path || '/';
|
|
438
|
+
console.log(`📁 OneDrive: ${displayPath}`);
|
|
439
|
+
console.log('━'.repeat(60));
|
|
440
|
+
|
|
441
|
+
items.forEach((item, index) => {
|
|
442
|
+
const icon = item.folder ? '📁' : '📄';
|
|
443
|
+
const type = item.folder ? 'Folder' : 'File';
|
|
444
|
+
const size = item.folder ? '-' : formatFileSize(item.size);
|
|
445
|
+
const name = truncate(item.name, 40);
|
|
446
|
+
const modified = formatDate(item.lastModifiedDateTime);
|
|
447
|
+
|
|
448
|
+
console.log(`[${index + 1}] ${icon} ${name}`);
|
|
449
|
+
console.log(` Type: ${type} | Size: ${size}`);
|
|
450
|
+
console.log(` Modified: ${modified}`);
|
|
451
|
+
console.log('');
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Output OneDrive item detail in text format
|
|
457
|
+
*/
|
|
458
|
+
export function outputOneDriveDetail(item, options = {}) {
|
|
459
|
+
const { json = false } = options;
|
|
460
|
+
|
|
461
|
+
if (json) {
|
|
462
|
+
// Ensure JSON output includes type field
|
|
463
|
+
const enrichedItem = {
|
|
464
|
+
...item,
|
|
465
|
+
type: item.folder ? 'folder' : 'file'
|
|
466
|
+
};
|
|
467
|
+
console.log(JSON.stringify(enrichedItem, null, 2));
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const icon = item.folder ? '📁' : '📄';
|
|
472
|
+
const type = item.folder ? 'Folder' : 'File';
|
|
473
|
+
|
|
474
|
+
console.log('━'.repeat(60));
|
|
475
|
+
console.log(`${icon} ${item.name}`);
|
|
476
|
+
console.log('━'.repeat(60));
|
|
477
|
+
console.log(`Type: ${type}`);
|
|
478
|
+
|
|
479
|
+
if (item.size !== undefined) {
|
|
480
|
+
console.log(`Size: ${formatFileSize(item.size)}`);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (item.lastModifiedDateTime) {
|
|
484
|
+
console.log(`Modified: ${formatDate(item.lastModifiedDateTime)}`);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (item.createdDateTime) {
|
|
488
|
+
console.log(`Created: ${formatDate(item.createdDateTime)}`);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (item.webUrl) {
|
|
492
|
+
console.log(`Web URL: ${item.webUrl}`);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (item.folder && item.folder.childCount !== undefined) {
|
|
496
|
+
console.log(`Items inside: ${item.folder.childCount}`);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (item.file) {
|
|
500
|
+
if (item.file.mimeType) {
|
|
501
|
+
console.log(`MIME Type: ${item.file.mimeType}`);
|
|
502
|
+
}
|
|
503
|
+
if (item.file.hashes?.sha1Hash) {
|
|
504
|
+
console.log(`SHA1: ${item.file.hashes.sha1Hash}`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
console.log('━'.repeat(60));
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Output OneDrive operation result
|
|
513
|
+
*/
|
|
514
|
+
export function outputOneDriveResult(result, options = {}) {
|
|
515
|
+
const { json = false } = options;
|
|
516
|
+
|
|
517
|
+
if (json) {
|
|
518
|
+
console.log(JSON.stringify(result, null, 2));
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const statusEmoji = {
|
|
523
|
+
'uploaded': '✅',
|
|
524
|
+
'downloaded': '⬇️',
|
|
525
|
+
'created': '📁',
|
|
526
|
+
'deleted': '🗑️',
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
const emoji = statusEmoji[result.status] || '✅';
|
|
530
|
+
const action = result.status.charAt(0).toUpperCase() + result.status.slice(1);
|
|
531
|
+
|
|
532
|
+
console.log(`${emoji} ${action}!`);
|
|
533
|
+
|
|
534
|
+
if (result.name) {
|
|
535
|
+
console.log(` Name: ${result.name}`);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (result.path) {
|
|
539
|
+
console.log(` Path: ${result.path}`);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (result.size !== undefined) {
|
|
543
|
+
console.log(` Size: ${formatFileSize(result.size)}`);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (result.type) {
|
|
547
|
+
console.log(` Type: ${result.type}`);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (result.webUrl) {
|
|
551
|
+
console.log(` URL: ${result.webUrl}`);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Output OneDrive search results
|
|
557
|
+
*/
|
|
558
|
+
export function outputOneDriveSearchResults(results, options = {}) {
|
|
559
|
+
const { json = false, query = '' } = options;
|
|
560
|
+
|
|
561
|
+
if (json) {
|
|
562
|
+
// Ensure JSON output includes type field
|
|
563
|
+
const enrichedResults = results.map(item => ({
|
|
564
|
+
...item,
|
|
565
|
+
type: item.folder ? 'folder' : 'file'
|
|
566
|
+
}));
|
|
567
|
+
console.log(JSON.stringify(enrichedResults, null, 2));
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (!results || results.length === 0) {
|
|
572
|
+
console.log(`🔍 No results found for "${query}".`);
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
console.log(`🔍 Search results for "${query}" (${results.length} items)`);
|
|
577
|
+
console.log('━'.repeat(60));
|
|
578
|
+
|
|
579
|
+
results.forEach((item, index) => {
|
|
580
|
+
const icon = item.folder ? '📁' : '📄';
|
|
581
|
+
const type = item.folder ? 'Folder' : 'File';
|
|
582
|
+
const size = item.folder ? '-' : formatFileSize(item.size);
|
|
583
|
+
const name = truncate(item.name, 40);
|
|
584
|
+
|
|
585
|
+
console.log(`[${index + 1}] ${icon} ${name}`);
|
|
586
|
+
console.log(` Type: ${type} | Size: ${size}`);
|
|
587
|
+
if (item.webUrl) {
|
|
588
|
+
console.log(` URL: ${item.webUrl}`);
|
|
589
|
+
}
|
|
590
|
+
console.log('');
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Output OneDrive share result
|
|
596
|
+
*/
|
|
597
|
+
export function outputOneDriveShareResult(result, options = {}) {
|
|
598
|
+
const { json = false, path = '', type = 'view' } = options;
|
|
599
|
+
|
|
600
|
+
if (json) {
|
|
601
|
+
console.log(JSON.stringify(result, null, 2));
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const accessType = type === 'view' ? 'View-only' : 'Edit';
|
|
606
|
+
|
|
607
|
+
console.log(`🔗 Share link created!`);
|
|
608
|
+
console.log(` Path: ${path}`);
|
|
609
|
+
console.log(` Type: ${accessType}`);
|
|
610
|
+
console.log(` Link: ${result.link?.webUrl || 'N/A'}`);
|
|
611
|
+
console.log('');
|
|
612
|
+
console.log(' Anyone with this link can access the file.');
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Output OneDrive invite result
|
|
617
|
+
*/
|
|
618
|
+
export function outputOneDriveInviteResult(result, options = {}) {
|
|
619
|
+
const { json = false, path = '', recipients = [], role = 'read', sendInvitation = true } = options;
|
|
620
|
+
|
|
621
|
+
if (json) {
|
|
622
|
+
console.log(JSON.stringify(result, null, 2));
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const accessType = role === 'write' ? '编辑' : '查看';
|
|
627
|
+
const inviteStatus = sendInvitation ? '已发送邮件邀请' : '仅创建权限(未发送邮件)';
|
|
628
|
+
|
|
629
|
+
console.log(`📤 分享邀请创建成功!`);
|
|
630
|
+
console.log(` 文件路径: ${path}`);
|
|
631
|
+
console.log(` 权限类型: ${accessType}`);
|
|
632
|
+
console.log(` 邀请状态: ${inviteStatus}`);
|
|
633
|
+
console.log(` 受邀人数: ${recipients.length}`);
|
|
634
|
+
console.log('');
|
|
635
|
+
|
|
636
|
+
if (result.value && result.value.length > 0) {
|
|
637
|
+
console.log(' 受邀用户:');
|
|
638
|
+
result.value.forEach((permission, index) => {
|
|
639
|
+
const grantedTo = permission.grantedToIdentitiesV2?.[0] || permission.grantedTo;
|
|
640
|
+
const email = grantedTo?.user?.email || grantedTo?.user?.id || recipients[index] || '未知';
|
|
641
|
+
const roles = permission.roles?.join(', ') || role;
|
|
642
|
+
console.log(` [${index + 1}] ${email} (${roles})`);
|
|
643
|
+
|
|
644
|
+
if (permission.invitation?.email) {
|
|
645
|
+
console.log(` 邀请链接已发送到: ${permission.invitation.email}`);
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (sendInvitation) {
|
|
651
|
+
console.log('');
|
|
652
|
+
console.log(' ℹ️ 外部用户将收到邮件邀请,点击链接后需输入一次性验证码访问文件。');
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Output SharePoint site list in text format
|
|
658
|
+
*/
|
|
659
|
+
export function outputSharePointSiteList(sites, options = {}) {
|
|
660
|
+
const { json = false, search = '' } = options;
|
|
661
|
+
|
|
662
|
+
if (json) {
|
|
663
|
+
// Ensure JSON output includes name field
|
|
664
|
+
const enrichedSites = sites.map(site => ({
|
|
665
|
+
...site,
|
|
666
|
+
name: site.displayName || site.name || 'Untitled'
|
|
667
|
+
}));
|
|
668
|
+
console.log(JSON.stringify(enrichedSites, null, 2));
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
if (!sites || sites.length === 0) {
|
|
673
|
+
console.log(`🏢 No SharePoint sites found${search ? ` for "${search}"` : ''}.`);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const header = search ? `Search results for "${search}"` : 'Followed Sites';
|
|
678
|
+
console.log(`🏢 SharePoint Sites - ${header}`);
|
|
679
|
+
console.log('━'.repeat(60));
|
|
680
|
+
|
|
681
|
+
sites.forEach((site, index) => {
|
|
682
|
+
const name = site.displayName || site.name;
|
|
683
|
+
const desc = truncate(site.description || '', 50);
|
|
684
|
+
const url = site.webUrl || 'N/A';
|
|
685
|
+
|
|
686
|
+
console.log(`[${index + 1}] 🏢 ${name}`);
|
|
687
|
+
if (desc) {
|
|
688
|
+
console.log(` ${desc}`);
|
|
689
|
+
}
|
|
690
|
+
console.log(` URL: ${url}`);
|
|
691
|
+
console.log(` ID: ${site.id?.slice(0, 40)}...`);
|
|
692
|
+
console.log('');
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Output SharePoint list collection in text format
|
|
698
|
+
*/
|
|
699
|
+
export function outputSharePointLists(lists, options = {}) {
|
|
700
|
+
const { json = false, site = '' } = options;
|
|
701
|
+
|
|
702
|
+
if (json) {
|
|
703
|
+
// Ensure JSON output includes name field
|
|
704
|
+
const enrichedLists = lists.map(list => ({
|
|
705
|
+
...list,
|
|
706
|
+
name: list.displayName || list.name || 'Untitled'
|
|
707
|
+
}));
|
|
708
|
+
console.log(JSON.stringify(enrichedLists, null, 2));
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (!lists || lists.length === 0) {
|
|
713
|
+
console.log(`📋 No lists found${site ? ` in site "${site}"` : ''}.`);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
console.log(`📋 SharePoint Lists${site ? ` - ${site}` : ''}`);
|
|
718
|
+
console.log('━'.repeat(60));
|
|
719
|
+
|
|
720
|
+
lists.forEach((list, index) => {
|
|
721
|
+
const name = list.displayName || list.name;
|
|
722
|
+
const desc = truncate(list.description || '', 50);
|
|
723
|
+
const url = list.webUrl || 'N/A';
|
|
724
|
+
const icon = list.list?.template === 'documentLibrary' ? '📁' : '📋';
|
|
725
|
+
|
|
726
|
+
console.log(`[${index + 1}] ${icon} ${name}`);
|
|
727
|
+
if (desc) {
|
|
728
|
+
console.log(` ${desc}`);
|
|
729
|
+
}
|
|
730
|
+
console.log(` URL: ${url}`);
|
|
731
|
+
console.log(` ID: ${list.id}`);
|
|
732
|
+
console.log('');
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Output SharePoint list items in text format
|
|
738
|
+
*/
|
|
739
|
+
export function outputSharePointItems(items, options = {}) {
|
|
740
|
+
const { json = false } = options;
|
|
741
|
+
|
|
742
|
+
if (json) {
|
|
743
|
+
console.log(JSON.stringify(items, null, 2));
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
if (!items || items.length === 0) {
|
|
748
|
+
console.log('📄 No items found.');
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
console.log(`📄 List Items (${items.length})`);
|
|
753
|
+
console.log('━'.repeat(60));
|
|
754
|
+
|
|
755
|
+
items.forEach((item, index) => {
|
|
756
|
+
const fields = item.fields || {};
|
|
757
|
+
const title = fields.Title || fields.title || `Item ${item.id}`;
|
|
758
|
+
|
|
759
|
+
console.log(`[${index + 1}] 📄 ${title}`);
|
|
760
|
+
console.log(` ID: ${item.id}`);
|
|
761
|
+
|
|
762
|
+
// Show some common fields
|
|
763
|
+
Object.keys(fields).forEach(key => {
|
|
764
|
+
if (key === 'Title' || key === 'title' || key === 'id' || key.startsWith('@')) {
|
|
765
|
+
return; // Skip title (already shown) and OData fields
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const value = fields[key];
|
|
769
|
+
if (value && typeof value !== 'object') {
|
|
770
|
+
const displayKey = key.replace(/([A-Z])/g, ' $1').trim();
|
|
771
|
+
console.log(` ${displayKey}: ${truncate(String(value), 50)}`);
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
console.log('');
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Output SharePoint search results in text format
|
|
781
|
+
*/
|
|
782
|
+
export function outputSharePointSearchResults(results, options = {}) {
|
|
783
|
+
const { json = false, query = '' } = options;
|
|
784
|
+
|
|
785
|
+
if (json) {
|
|
786
|
+
console.log(JSON.stringify(results, null, 2));
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (!results || results.length === 0) {
|
|
791
|
+
console.log(`🔍 No results found for "${query}".`);
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
console.log(`🔍 SharePoint Search - "${query}" (${results.length} results)`);
|
|
796
|
+
console.log('━'.repeat(60));
|
|
797
|
+
|
|
798
|
+
results.forEach((item, index) => {
|
|
799
|
+
const name = item.name || item.title || 'Untitled';
|
|
800
|
+
const type = item['@odata.type']?.split('.').pop() || 'Unknown';
|
|
801
|
+
let icon = '📄';
|
|
802
|
+
|
|
803
|
+
if (type === 'site') {
|
|
804
|
+
icon = '🏢';
|
|
805
|
+
} else if (type === 'driveItem' && item.folder) {
|
|
806
|
+
icon = '📁';
|
|
807
|
+
} else if (type === 'listItem') {
|
|
808
|
+
icon = '📋';
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
console.log(`[${index + 1}] ${icon} ${name}`);
|
|
812
|
+
console.log(` Type: ${type}`);
|
|
813
|
+
|
|
814
|
+
if (item.webUrl) {
|
|
815
|
+
console.log(` URL: ${item.webUrl}`);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
if (item.size !== undefined) {
|
|
819
|
+
console.log(` Size: ${formatFileSize(item.size)}`);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
console.log('');
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
export default {
|
|
827
|
+
outputMailList,
|
|
828
|
+
outputMailDetail,
|
|
829
|
+
outputSendResult,
|
|
830
|
+
outputAttachmentList,
|
|
831
|
+
outputAttachmentDownload,
|
|
832
|
+
outputSuccess,
|
|
833
|
+
outputCalendarList,
|
|
834
|
+
outputCalendarDetail,
|
|
835
|
+
outputCalendarResult,
|
|
836
|
+
outputOneDriveList,
|
|
837
|
+
outputOneDriveDetail,
|
|
838
|
+
outputOneDriveResult,
|
|
839
|
+
outputOneDriveSearchResults,
|
|
840
|
+
outputOneDriveShareResult,
|
|
841
|
+
outputOneDriveInviteResult,
|
|
842
|
+
outputSharePointSiteList,
|
|
843
|
+
outputSharePointLists,
|
|
844
|
+
outputSharePointItems,
|
|
845
|
+
outputSharePointSearchResults,
|
|
846
|
+
formatDate,
|
|
847
|
+
formatFileSize,
|
|
848
|
+
truncate,
|
|
849
|
+
stripHtml,
|
|
850
|
+
};
|