@jbctechsolutions/mcp-office365 2.5.1
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/LICENSE +21 -0
- package/README.md +667 -0
- package/dist/applescript/account-repository.d.ts +30 -0
- package/dist/applescript/account-repository.d.ts.map +1 -0
- package/dist/applescript/account-repository.js +38 -0
- package/dist/applescript/account-repository.js.map +1 -0
- package/dist/applescript/account-scripts.d.ts +21 -0
- package/dist/applescript/account-scripts.d.ts.map +1 -0
- package/dist/applescript/account-scripts.js +180 -0
- package/dist/applescript/account-scripts.js.map +1 -0
- package/dist/applescript/calendar-manager.d.ts +44 -0
- package/dist/applescript/calendar-manager.d.ts.map +1 -0
- package/dist/applescript/calendar-manager.js +92 -0
- package/dist/applescript/calendar-manager.js.map +1 -0
- package/dist/applescript/calendar-writer.d.ts +36 -0
- package/dist/applescript/calendar-writer.d.ts.map +1 -0
- package/dist/applescript/calendar-writer.js +94 -0
- package/dist/applescript/calendar-writer.js.map +1 -0
- package/dist/applescript/content-readers.d.ts +114 -0
- package/dist/applescript/content-readers.d.ts.map +1 -0
- package/dist/applescript/content-readers.js +328 -0
- package/dist/applescript/content-readers.js.map +1 -0
- package/dist/applescript/executor.d.ts +60 -0
- package/dist/applescript/executor.d.ts.map +1 -0
- package/dist/applescript/executor.js +173 -0
- package/dist/applescript/executor.js.map +1 -0
- package/dist/applescript/index.d.ts +20 -0
- package/dist/applescript/index.d.ts.map +1 -0
- package/dist/applescript/index.js +29 -0
- package/dist/applescript/index.js.map +1 -0
- package/dist/applescript/mail-sender.d.ts +38 -0
- package/dist/applescript/mail-sender.d.ts.map +1 -0
- package/dist/applescript/mail-sender.js +67 -0
- package/dist/applescript/mail-sender.js.map +1 -0
- package/dist/applescript/parser.d.ts +235 -0
- package/dist/applescript/parser.d.ts.map +1 -0
- package/dist/applescript/parser.js +496 -0
- package/dist/applescript/parser.js.map +1 -0
- package/dist/applescript/repository.d.ts +64 -0
- package/dist/applescript/repository.d.ts.map +1 -0
- package/dist/applescript/repository.js +444 -0
- package/dist/applescript/repository.js.map +1 -0
- package/dist/applescript/scripts.d.ts +265 -0
- package/dist/applescript/scripts.d.ts.map +1 -0
- package/dist/applescript/scripts.js +1483 -0
- package/dist/applescript/scripts.js.map +1 -0
- package/dist/approval/hash.d.ts +87 -0
- package/dist/approval/hash.d.ts.map +1 -0
- package/dist/approval/hash.js +102 -0
- package/dist/approval/hash.js.map +1 -0
- package/dist/approval/index.d.ts +13 -0
- package/dist/approval/index.d.ts.map +1 -0
- package/dist/approval/index.js +7 -0
- package/dist/approval/index.js.map +1 -0
- package/dist/approval/token-manager.d.ts +51 -0
- package/dist/approval/token-manager.d.ts.map +1 -0
- package/dist/approval/token-manager.js +111 -0
- package/dist/approval/token-manager.js.map +1 -0
- package/dist/approval/types.d.ts +44 -0
- package/dist/approval/types.d.ts.map +1 -0
- package/dist/approval/types.js +6 -0
- package/dist/approval/types.js.map +1 -0
- package/dist/cli.d.ts +30 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +143 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +27 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +42 -0
- package/dist/config.js.map +1 -0
- package/dist/database/connection.d.ts +77 -0
- package/dist/database/connection.d.ts.map +1 -0
- package/dist/database/connection.js +130 -0
- package/dist/database/connection.js.map +1 -0
- package/dist/database/index.d.ts +11 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +11 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/queries.d.ts +93 -0
- package/dist/database/queries.d.ts.map +1 -0
- package/dist/database/queries.js +430 -0
- package/dist/database/queries.js.map +1 -0
- package/dist/database/repository.d.ts +198 -0
- package/dist/database/repository.d.ts.map +1 -0
- package/dist/database/repository.js +199 -0
- package/dist/database/repository.js.map +1 -0
- package/dist/graph/attachments.d.ts +72 -0
- package/dist/graph/attachments.d.ts.map +1 -0
- package/dist/graph/attachments.js +207 -0
- package/dist/graph/attachments.js.map +1 -0
- package/dist/graph/auth/config.d.ts +34 -0
- package/dist/graph/auth/config.d.ts.map +1 -0
- package/dist/graph/auth/config.js +78 -0
- package/dist/graph/auth/config.js.map +1 -0
- package/dist/graph/auth/device-code-flow.d.ts +55 -0
- package/dist/graph/auth/device-code-flow.d.ts.map +1 -0
- package/dist/graph/auth/device-code-flow.js +180 -0
- package/dist/graph/auth/device-code-flow.js.map +1 -0
- package/dist/graph/auth/index.d.ts +13 -0
- package/dist/graph/auth/index.d.ts.map +1 -0
- package/dist/graph/auth/index.js +13 -0
- package/dist/graph/auth/index.js.map +1 -0
- package/dist/graph/auth/token-cache.d.ts +41 -0
- package/dist/graph/auth/token-cache.d.ts.map +1 -0
- package/dist/graph/auth/token-cache.js +105 -0
- package/dist/graph/auth/token-cache.js.map +1 -0
- package/dist/graph/client/batch.d.ts +38 -0
- package/dist/graph/client/batch.d.ts.map +1 -0
- package/dist/graph/client/batch.js +33 -0
- package/dist/graph/client/batch.js.map +1 -0
- package/dist/graph/client/cache.d.ts +64 -0
- package/dist/graph/client/cache.d.ts.map +1 -0
- package/dist/graph/client/cache.js +108 -0
- package/dist/graph/client/cache.js.map +1 -0
- package/dist/graph/client/graph-client.d.ts +630 -0
- package/dist/graph/client/graph-client.d.ts.map +1 -0
- package/dist/graph/client/graph-client.js +1771 -0
- package/dist/graph/client/graph-client.js.map +1 -0
- package/dist/graph/client/index.d.ts +12 -0
- package/dist/graph/client/index.d.ts.map +1 -0
- package/dist/graph/client/index.js +12 -0
- package/dist/graph/client/index.js.map +1 -0
- package/dist/graph/content-readers.d.ts +106 -0
- package/dist/graph/content-readers.d.ts.map +1 -0
- package/dist/graph/content-readers.js +321 -0
- package/dist/graph/content-readers.js.map +1 -0
- package/dist/graph/index.d.ts +18 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +23 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/mailbox-adapter.d.ts +30 -0
- package/dist/graph/mailbox-adapter.d.ts.map +1 -0
- package/dist/graph/mailbox-adapter.js +59 -0
- package/dist/graph/mailbox-adapter.js.map +1 -0
- package/dist/graph/mappers/contact-mapper.d.ts +14 -0
- package/dist/graph/mappers/contact-mapper.d.ts.map +1 -0
- package/dist/graph/mappers/contact-mapper.js +20 -0
- package/dist/graph/mappers/contact-mapper.js.map +1 -0
- package/dist/graph/mappers/email-mapper.d.ts +14 -0
- package/dist/graph/mappers/email-mapper.d.ts.map +1 -0
- package/dist/graph/mappers/email-mapper.js +44 -0
- package/dist/graph/mappers/email-mapper.js.map +1 -0
- package/dist/graph/mappers/event-mapper.d.ts +14 -0
- package/dist/graph/mappers/event-mapper.d.ts.map +1 -0
- package/dist/graph/mappers/event-mapper.js +31 -0
- package/dist/graph/mappers/event-mapper.js.map +1 -0
- package/dist/graph/mappers/folder-mapper.d.ts +22 -0
- package/dist/graph/mappers/folder-mapper.d.ts.map +1 -0
- package/dist/graph/mappers/folder-mapper.js +51 -0
- package/dist/graph/mappers/folder-mapper.js.map +1 -0
- package/dist/graph/mappers/index.d.ts +16 -0
- package/dist/graph/mappers/index.d.ts.map +1 -0
- package/dist/graph/mappers/index.js +16 -0
- package/dist/graph/mappers/index.js.map +1 -0
- package/dist/graph/mappers/task-mapper.d.ts +20 -0
- package/dist/graph/mappers/task-mapper.d.ts.map +1 -0
- package/dist/graph/mappers/task-mapper.js +27 -0
- package/dist/graph/mappers/task-mapper.js.map +1 -0
- package/dist/graph/mappers/utils.d.ts +97 -0
- package/dist/graph/mappers/utils.d.ts.map +1 -0
- package/dist/graph/mappers/utils.js +186 -0
- package/dist/graph/mappers/utils.js.map +1 -0
- package/dist/graph/repository.d.ts +1104 -0
- package/dist/graph/repository.d.ts.map +1 -0
- package/dist/graph/repository.js +2999 -0
- package/dist/graph/repository.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6052 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/html-stripper.d.ts +41 -0
- package/dist/parsers/html-stripper.d.ts.map +1 -0
- package/dist/parsers/html-stripper.js +179 -0
- package/dist/parsers/html-stripper.js.map +1 -0
- package/dist/parsers/index.d.ts +12 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/index.js +12 -0
- package/dist/parsers/index.js.map +1 -0
- package/dist/parsers/olk15.d.ts +87 -0
- package/dist/parsers/olk15.d.ts.map +1 -0
- package/dist/parsers/olk15.js +368 -0
- package/dist/parsers/olk15.js.map +1 -0
- package/dist/signature.d.ts +22 -0
- package/dist/signature.d.ts.map +1 -0
- package/dist/signature.js +89 -0
- package/dist/signature.js.map +1 -0
- package/dist/tools/calendar-permissions.d.ts +79 -0
- package/dist/tools/calendar-permissions.d.ts.map +1 -0
- package/dist/tools/calendar-permissions.js +121 -0
- package/dist/tools/calendar-permissions.js.map +1 -0
- package/dist/tools/calendar.d.ts +208 -0
- package/dist/tools/calendar.d.ts.map +1 -0
- package/dist/tools/calendar.js +247 -0
- package/dist/tools/calendar.js.map +1 -0
- package/dist/tools/categories.d.ts +94 -0
- package/dist/tools/categories.d.ts.map +1 -0
- package/dist/tools/categories.js +117 -0
- package/dist/tools/categories.js.map +1 -0
- package/dist/tools/checklist-items.d.ts +89 -0
- package/dist/tools/checklist-items.d.ts.map +1 -0
- package/dist/tools/checklist-items.js +140 -0
- package/dist/tools/checklist-items.js.map +1 -0
- package/dist/tools/contacts.d.ts +94 -0
- package/dist/tools/contacts.d.ts.map +1 -0
- package/dist/tools/contacts.js +134 -0
- package/dist/tools/contacts.js.map +1 -0
- package/dist/tools/excel.d.ts +96 -0
- package/dist/tools/excel.d.ts.map +1 -0
- package/dist/tools/excel.js +165 -0
- package/dist/tools/excel.js.map +1 -0
- package/dist/tools/focused-overrides.d.ts +70 -0
- package/dist/tools/focused-overrides.d.ts.map +1 -0
- package/dist/tools/focused-overrides.js +117 -0
- package/dist/tools/focused-overrides.js.map +1 -0
- package/dist/tools/index.d.ts +22 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +34 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/linked-resources.d.ts +74 -0
- package/dist/tools/linked-resources.d.ts.map +1 -0
- package/dist/tools/linked-resources.js +122 -0
- package/dist/tools/linked-resources.js.map +1 -0
- package/dist/tools/mail-rules.d.ts +98 -0
- package/dist/tools/mail-rules.d.ts.map +1 -0
- package/dist/tools/mail-rules.js +169 -0
- package/dist/tools/mail-rules.js.map +1 -0
- package/dist/tools/mail-send.d.ts +314 -0
- package/dist/tools/mail-send.d.ts.map +1 -0
- package/dist/tools/mail-send.js +555 -0
- package/dist/tools/mail-send.js.map +1 -0
- package/dist/tools/mail.d.ts +127 -0
- package/dist/tools/mail.d.ts.map +1 -0
- package/dist/tools/mail.js +311 -0
- package/dist/tools/mail.js.map +1 -0
- package/dist/tools/mailbox-organization.d.ts +301 -0
- package/dist/tools/mailbox-organization.d.ts.map +1 -0
- package/dist/tools/mailbox-organization.js +541 -0
- package/dist/tools/mailbox-organization.js.map +1 -0
- package/dist/tools/meetings.d.ts +114 -0
- package/dist/tools/meetings.d.ts.map +1 -0
- package/dist/tools/meetings.js +110 -0
- package/dist/tools/meetings.js.map +1 -0
- package/dist/tools/notes.d.ts +74 -0
- package/dist/tools/notes.d.ts.map +1 -0
- package/dist/tools/notes.js +136 -0
- package/dist/tools/notes.js.map +1 -0
- package/dist/tools/onedrive.d.ts +194 -0
- package/dist/tools/onedrive.d.ts.map +1 -0
- package/dist/tools/onedrive.js +257 -0
- package/dist/tools/onedrive.js.map +1 -0
- package/dist/tools/people.d.ts +129 -0
- package/dist/tools/people.d.ts.map +1 -0
- package/dist/tools/people.js +195 -0
- package/dist/tools/people.js.map +1 -0
- package/dist/tools/planner-visualization.d.ts +91 -0
- package/dist/tools/planner-visualization.d.ts.map +1 -0
- package/dist/tools/planner-visualization.js +192 -0
- package/dist/tools/planner-visualization.js.map +1 -0
- package/dist/tools/planner.d.ts +288 -0
- package/dist/tools/planner.d.ts.map +1 -0
- package/dist/tools/planner.js +368 -0
- package/dist/tools/planner.js.map +1 -0
- package/dist/tools/scheduling.d.ts +49 -0
- package/dist/tools/scheduling.d.ts.map +1 -0
- package/dist/tools/scheduling.js +115 -0
- package/dist/tools/scheduling.js.map +1 -0
- package/dist/tools/sharepoint.d.ts +115 -0
- package/dist/tools/sharepoint.d.ts.map +1 -0
- package/dist/tools/sharepoint.js +99 -0
- package/dist/tools/sharepoint.js.map +1 -0
- package/dist/tools/task-attachments.d.ts +74 -0
- package/dist/tools/task-attachments.d.ts.map +1 -0
- package/dist/tools/task-attachments.js +122 -0
- package/dist/tools/task-attachments.js.map +1 -0
- package/dist/tools/tasks.d.ts +74 -0
- package/dist/tools/tasks.d.ts.map +1 -0
- package/dist/tools/tasks.js +126 -0
- package/dist/tools/tasks.js.map +1 -0
- package/dist/tools/teams.d.ts +389 -0
- package/dist/tools/teams.d.ts.map +1 -0
- package/dist/tools/teams.js +546 -0
- package/dist/tools/teams.js.map +1 -0
- package/dist/types/calendar.d.ts +60 -0
- package/dist/types/calendar.d.ts.map +1 -0
- package/dist/types/calendar.js +15 -0
- package/dist/types/calendar.js.map +1 -0
- package/dist/types/contacts.d.ts +96 -0
- package/dist/types/contacts.d.ts.map +1 -0
- package/dist/types/contacts.js +41 -0
- package/dist/types/contacts.js.map +1 -0
- package/dist/types/index.d.ts +15 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +16 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/mail.d.ts +108 -0
- package/dist/types/mail.d.ts.map +1 -0
- package/dist/types/mail.js +40 -0
- package/dist/types/mail.js.map +1 -0
- package/dist/types/notes.d.ts +26 -0
- package/dist/types/notes.d.ts.map +1 -0
- package/dist/types/notes.js +6 -0
- package/dist/types/notes.js.map +1 -0
- package/dist/types/tasks.d.ts +31 -0
- package/dist/types/tasks.d.ts.map +1 -0
- package/dist/types/tasks.js +6 -0
- package/dist/types/tasks.js.map +1 -0
- package/dist/utils/dates.d.ts +66 -0
- package/dist/utils/dates.d.ts.map +1 -0
- package/dist/utils/dates.js +94 -0
- package/dist/utils/dates.js.map +1 -0
- package/dist/utils/errors.d.ts +218 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +306 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/index.d.ts +10 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +10 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/visualization/html.d.ts +26 -0
- package/dist/visualization/html.d.ts.map +1 -0
- package/dist/visualization/html.js +306 -0
- package/dist/visualization/html.js.map +1 -0
- package/dist/visualization/markdown.d.ts +25 -0
- package/dist/visualization/markdown.d.ts.map +1 -0
- package/dist/visualization/markdown.js +186 -0
- package/dist/visualization/markdown.js.map +1 -0
- package/dist/visualization/mermaid.d.ts +25 -0
- package/dist/visualization/mermaid.d.ts.map +1 -0
- package/dist/visualization/mermaid.js +158 -0
- package/dist/visualization/mermaid.js.map +1 -0
- package/dist/visualization/svg.d.ts +25 -0
- package/dist/visualization/svg.d.ts.map +1 -0
- package/dist/visualization/svg.js +282 -0
- package/dist/visualization/svg.js.map +1 -0
- package/dist/visualization/types.d.ts +43 -0
- package/dist/visualization/types.d.ts.map +1 -0
- package/dist/visualization/types.js +34 -0
- package/dist/visualization/types.js.map +1 -0
- package/package.json +88 -0
|
@@ -0,0 +1,2999 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 JBC Tech Solutions, LLC
|
|
3
|
+
* Licensed under the MIT License. See LICENSE file in the project root.
|
|
4
|
+
*/
|
|
5
|
+
import { GraphClient } from './client/index.js';
|
|
6
|
+
import { mapMailFolderToRow, mapCalendarToFolderRow, mapMessageToEmailRow, mapEventToEventRow, mapContactToContactRow, mapTaskToTaskRow, hashStringToNumber, } from './mappers/index.js';
|
|
7
|
+
import { downloadAttachment, getDownloadDir } from './attachments.js';
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
/**
|
|
11
|
+
* Repository implementation using Microsoft Graph API.
|
|
12
|
+
*
|
|
13
|
+
* Provides read-only access to Outlook data via the Graph API.
|
|
14
|
+
*/
|
|
15
|
+
export class GraphRepository {
|
|
16
|
+
client;
|
|
17
|
+
deltaLinks = new Map();
|
|
18
|
+
idCache = {
|
|
19
|
+
folders: new Map(),
|
|
20
|
+
messages: new Map(),
|
|
21
|
+
conversations: new Map(),
|
|
22
|
+
events: new Map(),
|
|
23
|
+
contacts: new Map(),
|
|
24
|
+
tasks: new Map(),
|
|
25
|
+
taskLists: new Map(),
|
|
26
|
+
attachments: new Map(),
|
|
27
|
+
rules: new Map(),
|
|
28
|
+
contactFolders: new Map(),
|
|
29
|
+
categories: new Map(),
|
|
30
|
+
focusedOverrides: new Map(),
|
|
31
|
+
calendarGroups: new Map(),
|
|
32
|
+
calendars: new Map(),
|
|
33
|
+
calendarPermissions: new Map(),
|
|
34
|
+
teams: new Map(),
|
|
35
|
+
channels: new Map(),
|
|
36
|
+
channelMessages: new Map(),
|
|
37
|
+
chats: new Map(),
|
|
38
|
+
chatMessages: new Map(),
|
|
39
|
+
checklistItems: new Map(),
|
|
40
|
+
linkedResources: new Map(),
|
|
41
|
+
taskAttachments: new Map(),
|
|
42
|
+
plans: new Map(),
|
|
43
|
+
plannerBuckets: new Map(),
|
|
44
|
+
plannerTasks: new Map(),
|
|
45
|
+
plannerTaskDetails: new Map(),
|
|
46
|
+
onlineMeetings: new Map(),
|
|
47
|
+
recordings: new Map(),
|
|
48
|
+
transcripts: new Map(),
|
|
49
|
+
driveItems: new Map(),
|
|
50
|
+
sites: new Map(),
|
|
51
|
+
documentLibraries: new Map(),
|
|
52
|
+
libraryDriveItems: new Map(),
|
|
53
|
+
};
|
|
54
|
+
constructor(deviceCodeCallback) {
|
|
55
|
+
this.client = new GraphClient(deviceCodeCallback);
|
|
56
|
+
}
|
|
57
|
+
// ===========================================================================
|
|
58
|
+
// Folders
|
|
59
|
+
// ===========================================================================
|
|
60
|
+
listFolders() {
|
|
61
|
+
// Note: Graph API is async, but IRepository interface is sync
|
|
62
|
+
// We need to use a sync wrapper or change the interface
|
|
63
|
+
// For now, we'll throw and require the async version
|
|
64
|
+
throw new Error('Use listFoldersAsync() for Graph repository');
|
|
65
|
+
}
|
|
66
|
+
async listFoldersAsync() {
|
|
67
|
+
const folders = await this.client.listMailFolders();
|
|
68
|
+
// Update ID cache
|
|
69
|
+
for (const folder of folders) {
|
|
70
|
+
if (folder.id != null) {
|
|
71
|
+
const numericId = hashStringToNumber(folder.id);
|
|
72
|
+
this.idCache.folders.set(numericId, folder.id);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return folders.map(mapMailFolderToRow);
|
|
76
|
+
}
|
|
77
|
+
getFolder(_id) {
|
|
78
|
+
throw new Error('Use getFolderAsync() for Graph repository');
|
|
79
|
+
}
|
|
80
|
+
async getFolderAsync(id) {
|
|
81
|
+
const graphId = this.idCache.folders.get(id);
|
|
82
|
+
if (graphId == null) {
|
|
83
|
+
// Try to find it by listing all folders
|
|
84
|
+
await this.listFoldersAsync();
|
|
85
|
+
const refreshedGraphId = this.idCache.folders.get(id);
|
|
86
|
+
if (refreshedGraphId == null) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
const folder = await this.client.getMailFolder(refreshedGraphId);
|
|
90
|
+
return folder != null ? mapMailFolderToRow(folder) : undefined;
|
|
91
|
+
}
|
|
92
|
+
const folder = await this.client.getMailFolder(graphId);
|
|
93
|
+
return folder != null ? mapMailFolderToRow(folder) : undefined;
|
|
94
|
+
}
|
|
95
|
+
// ===========================================================================
|
|
96
|
+
// Emails
|
|
97
|
+
// ===========================================================================
|
|
98
|
+
listEmails(_folderId, _limit, _offset) {
|
|
99
|
+
throw new Error('Use listEmailsAsync() for Graph repository');
|
|
100
|
+
}
|
|
101
|
+
async listEmailsAsync(folderId, limit, offset) {
|
|
102
|
+
const graphFolderId = this.idCache.folders.get(folderId);
|
|
103
|
+
if (graphFolderId == null) {
|
|
104
|
+
// Refresh folder cache
|
|
105
|
+
await this.listFoldersAsync();
|
|
106
|
+
const refreshedId = this.idCache.folders.get(folderId);
|
|
107
|
+
if (refreshedId == null) {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
return this.listEmailsWithGraphId(refreshedId, limit, offset);
|
|
111
|
+
}
|
|
112
|
+
return this.listEmailsWithGraphId(graphFolderId, limit, offset);
|
|
113
|
+
}
|
|
114
|
+
async listEmailsWithGraphId(folderId, limit, offset) {
|
|
115
|
+
const messages = await this.client.listMessages(folderId, limit, offset);
|
|
116
|
+
// Update ID cache
|
|
117
|
+
for (const message of messages) {
|
|
118
|
+
if (message.id != null) {
|
|
119
|
+
const numericId = hashStringToNumber(message.id);
|
|
120
|
+
this.idCache.messages.set(numericId, message.id);
|
|
121
|
+
}
|
|
122
|
+
if (message.conversationId != null) {
|
|
123
|
+
this.idCache.conversations.set(hashStringToNumber(message.conversationId), message.conversationId);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return messages.map((m) => mapMessageToEmailRow(m, folderId));
|
|
127
|
+
}
|
|
128
|
+
listUnreadEmails(_folderId, _limit, _offset) {
|
|
129
|
+
throw new Error('Use listUnreadEmailsAsync() for Graph repository');
|
|
130
|
+
}
|
|
131
|
+
async listUnreadEmailsAsync(folderId, limit, offset) {
|
|
132
|
+
const graphFolderId = this.idCache.folders.get(folderId);
|
|
133
|
+
if (graphFolderId == null) {
|
|
134
|
+
await this.listFoldersAsync();
|
|
135
|
+
const refreshedId = this.idCache.folders.get(folderId);
|
|
136
|
+
if (refreshedId == null) {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
return this.listUnreadEmailsWithGraphId(refreshedId, limit, offset);
|
|
140
|
+
}
|
|
141
|
+
return this.listUnreadEmailsWithGraphId(graphFolderId, limit, offset);
|
|
142
|
+
}
|
|
143
|
+
async listUnreadEmailsWithGraphId(folderId, limit, offset) {
|
|
144
|
+
const messages = await this.client.listUnreadMessages(folderId, limit, offset);
|
|
145
|
+
for (const message of messages) {
|
|
146
|
+
if (message.id != null) {
|
|
147
|
+
const numericId = hashStringToNumber(message.id);
|
|
148
|
+
this.idCache.messages.set(numericId, message.id);
|
|
149
|
+
}
|
|
150
|
+
if (message.conversationId != null) {
|
|
151
|
+
this.idCache.conversations.set(hashStringToNumber(message.conversationId), message.conversationId);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return messages.map((m) => mapMessageToEmailRow(m, folderId));
|
|
155
|
+
}
|
|
156
|
+
searchEmails(_query, _limit) {
|
|
157
|
+
throw new Error('Use searchEmailsAsync() for Graph repository');
|
|
158
|
+
}
|
|
159
|
+
async searchEmailsAsync(query, limit) {
|
|
160
|
+
const messages = await this.client.searchMessages(query, limit);
|
|
161
|
+
for (const message of messages) {
|
|
162
|
+
if (message.id != null) {
|
|
163
|
+
const numericId = hashStringToNumber(message.id);
|
|
164
|
+
this.idCache.messages.set(numericId, message.id);
|
|
165
|
+
}
|
|
166
|
+
if (message.conversationId != null) {
|
|
167
|
+
this.idCache.conversations.set(hashStringToNumber(message.conversationId), message.conversationId);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return messages.map((m) => mapMessageToEmailRow(m));
|
|
171
|
+
}
|
|
172
|
+
searchEmailsInFolder(_folderId, _query, _limit) {
|
|
173
|
+
throw new Error('Use searchEmailsInFolderAsync() for Graph repository');
|
|
174
|
+
}
|
|
175
|
+
async searchEmailsInFolderAsync(folderId, query, limit) {
|
|
176
|
+
const graphFolderId = this.idCache.folders.get(folderId);
|
|
177
|
+
if (graphFolderId == null) {
|
|
178
|
+
await this.listFoldersAsync();
|
|
179
|
+
const refreshedId = this.idCache.folders.get(folderId);
|
|
180
|
+
if (refreshedId == null) {
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
return this.searchEmailsInFolderWithGraphId(refreshedId, query, limit);
|
|
184
|
+
}
|
|
185
|
+
return this.searchEmailsInFolderWithGraphId(graphFolderId, query, limit);
|
|
186
|
+
}
|
|
187
|
+
async searchEmailsInFolderWithGraphId(folderId, query, limit) {
|
|
188
|
+
const messages = await this.client.searchMessagesInFolder(folderId, query, limit);
|
|
189
|
+
for (const message of messages) {
|
|
190
|
+
if (message.id != null) {
|
|
191
|
+
const numericId = hashStringToNumber(message.id);
|
|
192
|
+
this.idCache.messages.set(numericId, message.id);
|
|
193
|
+
}
|
|
194
|
+
if (message.conversationId != null) {
|
|
195
|
+
this.idCache.conversations.set(hashStringToNumber(message.conversationId), message.conversationId);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return messages.map((m) => mapMessageToEmailRow(m, folderId));
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Advanced search using raw KQL query syntax.
|
|
202
|
+
*/
|
|
203
|
+
async searchEmailsAdvancedAsync(query, limit) {
|
|
204
|
+
const messages = await this.client.searchMessagesKql(query, limit);
|
|
205
|
+
for (const msg of messages) {
|
|
206
|
+
if (msg.id != null) {
|
|
207
|
+
this.idCache.messages.set(hashStringToNumber(msg.id), msg.id);
|
|
208
|
+
}
|
|
209
|
+
if (msg.conversationId != null) {
|
|
210
|
+
this.idCache.conversations.set(hashStringToNumber(msg.conversationId), msg.conversationId);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return messages.map((m) => mapMessageToEmailRow(m));
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Advanced search in a specific folder using raw KQL query syntax.
|
|
217
|
+
*/
|
|
218
|
+
async searchEmailsAdvancedInFolderAsync(folderId, query, limit) {
|
|
219
|
+
const graphFolderId = this.idCache.folders.get(folderId);
|
|
220
|
+
if (graphFolderId == null) {
|
|
221
|
+
throw new Error(`Folder ID ${folderId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
222
|
+
}
|
|
223
|
+
const messages = await this.client.searchMessagesKqlInFolder(graphFolderId, query, limit);
|
|
224
|
+
for (const msg of messages) {
|
|
225
|
+
if (msg.id != null) {
|
|
226
|
+
this.idCache.messages.set(hashStringToNumber(msg.id), msg.id);
|
|
227
|
+
}
|
|
228
|
+
if (msg.conversationId != null) {
|
|
229
|
+
this.idCache.conversations.set(hashStringToNumber(msg.conversationId), msg.conversationId);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return messages.map((m) => mapMessageToEmailRow(m));
|
|
233
|
+
}
|
|
234
|
+
async checkNewEmailsAsync(folderId) {
|
|
235
|
+
const graphFolderId = this.idCache.folders.get(folderId);
|
|
236
|
+
if (graphFolderId == null)
|
|
237
|
+
throw new Error(`Folder ID ${folderId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
238
|
+
const existingDeltaLink = this.deltaLinks.get(folderId);
|
|
239
|
+
const isInitialSync = existingDeltaLink == null;
|
|
240
|
+
const { messages, deltaLink } = await this.client.getMessagesDelta(graphFolderId, existingDeltaLink);
|
|
241
|
+
if (deltaLink) {
|
|
242
|
+
this.deltaLinks.set(folderId, deltaLink);
|
|
243
|
+
}
|
|
244
|
+
for (const msg of messages) {
|
|
245
|
+
if (msg.id != null) {
|
|
246
|
+
this.idCache.messages.set(hashStringToNumber(msg.id), msg.id);
|
|
247
|
+
}
|
|
248
|
+
if (msg.conversationId != null) {
|
|
249
|
+
this.idCache.conversations.set(hashStringToNumber(msg.conversationId), msg.conversationId);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const activeMessages = messages.filter((m) => m['@removed'] == null);
|
|
253
|
+
return {
|
|
254
|
+
emails: activeMessages.map((m) => mapMessageToEmailRow(m)),
|
|
255
|
+
isInitialSync,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
getEmail(_id) {
|
|
259
|
+
throw new Error('Use getEmailAsync() for Graph repository');
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Populates the message ID cache by listing messages from mail folders.
|
|
263
|
+
* Used as a fallback when getEmailAsync is called with an ID not yet in cache
|
|
264
|
+
* (e.g. after server restart or when list_emails/search_emails wasn't called first).
|
|
265
|
+
*/
|
|
266
|
+
async refreshMessageCacheForGetEmail(targetId) {
|
|
267
|
+
let folders;
|
|
268
|
+
try {
|
|
269
|
+
folders = await this.listFoldersAsync();
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
if (folders.length === 0)
|
|
275
|
+
return false;
|
|
276
|
+
const MESSAGE_LIMIT_PER_FOLDER = 100;
|
|
277
|
+
const MAX_FOLDERS_TO_SCAN = 15;
|
|
278
|
+
for (let i = 0; i < Math.min(folders.length, MAX_FOLDERS_TO_SCAN); i++) {
|
|
279
|
+
const folder = folders[i];
|
|
280
|
+
const graphFolderId = this.idCache.folders.get(folder.id);
|
|
281
|
+
if (graphFolderId == null)
|
|
282
|
+
continue;
|
|
283
|
+
let messages;
|
|
284
|
+
try {
|
|
285
|
+
messages = await this.client.listMessages(graphFolderId, MESSAGE_LIMIT_PER_FOLDER, 0);
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (!Array.isArray(messages))
|
|
291
|
+
continue;
|
|
292
|
+
for (const message of messages) {
|
|
293
|
+
if (message.id != null) {
|
|
294
|
+
const numericId = hashStringToNumber(message.id);
|
|
295
|
+
this.idCache.messages.set(numericId, message.id);
|
|
296
|
+
if (numericId === targetId)
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
if (message.conversationId != null) {
|
|
300
|
+
this.idCache.conversations.set(hashStringToNumber(message.conversationId), message.conversationId);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return this.idCache.messages.has(targetId);
|
|
305
|
+
}
|
|
306
|
+
async getEmailAsync(id) {
|
|
307
|
+
let graphId = this.idCache.messages.get(id);
|
|
308
|
+
if (graphId == null) {
|
|
309
|
+
const found = await this.refreshMessageCacheForGetEmail(id);
|
|
310
|
+
if (found)
|
|
311
|
+
graphId = this.idCache.messages.get(id) ?? undefined;
|
|
312
|
+
}
|
|
313
|
+
if (graphId == null) {
|
|
314
|
+
return undefined;
|
|
315
|
+
}
|
|
316
|
+
const message = await this.client.getMessage(graphId);
|
|
317
|
+
return message != null ? mapMessageToEmailRow(message) : undefined;
|
|
318
|
+
}
|
|
319
|
+
getUnreadCount() {
|
|
320
|
+
throw new Error('Use getUnreadCountAsync() for Graph repository');
|
|
321
|
+
}
|
|
322
|
+
async getUnreadCountAsync() {
|
|
323
|
+
const folders = await this.client.listMailFolders();
|
|
324
|
+
return folders.reduce((sum, f) => sum + (f.unreadItemCount ?? 0), 0);
|
|
325
|
+
}
|
|
326
|
+
getUnreadCountByFolder(_folderId) {
|
|
327
|
+
throw new Error('Use getUnreadCountByFolderAsync() for Graph repository');
|
|
328
|
+
}
|
|
329
|
+
async getUnreadCountByFolderAsync(folderId) {
|
|
330
|
+
const graphId = this.idCache.folders.get(folderId);
|
|
331
|
+
if (graphId == null) {
|
|
332
|
+
await this.listFoldersAsync();
|
|
333
|
+
const refreshedId = this.idCache.folders.get(folderId);
|
|
334
|
+
if (refreshedId == null) {
|
|
335
|
+
return 0;
|
|
336
|
+
}
|
|
337
|
+
const folder = await this.client.getMailFolder(refreshedId);
|
|
338
|
+
return folder?.unreadItemCount ?? 0;
|
|
339
|
+
}
|
|
340
|
+
const folder = await this.client.getMailFolder(graphId);
|
|
341
|
+
return folder?.unreadItemCount ?? 0;
|
|
342
|
+
}
|
|
343
|
+
// ===========================================================================
|
|
344
|
+
// Conversation / Thread
|
|
345
|
+
// ===========================================================================
|
|
346
|
+
/**
|
|
347
|
+
* Lists all messages in a conversation thread.
|
|
348
|
+
*
|
|
349
|
+
* Looks up the message to get its conversationId, resolves the Graph string
|
|
350
|
+
* conversationId from cache, then queries for all messages with that ID.
|
|
351
|
+
*/
|
|
352
|
+
async listConversationAsync(messageId, limit) {
|
|
353
|
+
const email = await this.getEmailAsync(messageId);
|
|
354
|
+
if (email == null)
|
|
355
|
+
throw new Error(`Message ID ${messageId} not found`);
|
|
356
|
+
if (email.conversationId == null)
|
|
357
|
+
throw new Error('Message has no conversation ID');
|
|
358
|
+
const graphConversationId = this.idCache.conversations.get(email.conversationId);
|
|
359
|
+
if (graphConversationId == null)
|
|
360
|
+
throw new Error('Conversation ID not found in cache. Try fetching the email first to populate the cache.');
|
|
361
|
+
const messages = await this.client.listConversationMessages(graphConversationId, limit);
|
|
362
|
+
for (const msg of messages) {
|
|
363
|
+
if (msg.id != null) {
|
|
364
|
+
this.idCache.messages.set(hashStringToNumber(msg.id), msg.id);
|
|
365
|
+
}
|
|
366
|
+
if (msg.conversationId != null) {
|
|
367
|
+
this.idCache.conversations.set(hashStringToNumber(msg.conversationId), msg.conversationId);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return messages.map((m) => mapMessageToEmailRow(m));
|
|
371
|
+
}
|
|
372
|
+
// ===========================================================================
|
|
373
|
+
// Calendar
|
|
374
|
+
// ===========================================================================
|
|
375
|
+
listCalendars() {
|
|
376
|
+
throw new Error('Use listCalendarsAsync() for Graph repository');
|
|
377
|
+
}
|
|
378
|
+
async listCalendarsAsync() {
|
|
379
|
+
const calendars = await this.client.listCalendars();
|
|
380
|
+
for (const calendar of calendars) {
|
|
381
|
+
if (calendar.id != null) {
|
|
382
|
+
const numericId = hashStringToNumber(calendar.id);
|
|
383
|
+
this.idCache.folders.set(numericId, calendar.id);
|
|
384
|
+
this.idCache.calendars.set(numericId, calendar.id);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return calendars.map(mapCalendarToFolderRow);
|
|
388
|
+
}
|
|
389
|
+
listEvents(_limit) {
|
|
390
|
+
throw new Error('Use listEventsAsync() for Graph repository');
|
|
391
|
+
}
|
|
392
|
+
async listEventsAsync(limit) {
|
|
393
|
+
const events = await this.client.listEvents(limit);
|
|
394
|
+
for (const event of events) {
|
|
395
|
+
if (event.id != null) {
|
|
396
|
+
const numericId = hashStringToNumber(event.id);
|
|
397
|
+
this.idCache.events.set(numericId, event.id);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return events.map((e) => mapEventToEventRow(e));
|
|
401
|
+
}
|
|
402
|
+
listEventsByFolder(_folderId, _limit) {
|
|
403
|
+
throw new Error('Use listEventsByFolderAsync() for Graph repository');
|
|
404
|
+
}
|
|
405
|
+
async listEventsByFolderAsync(folderId, limit) {
|
|
406
|
+
const graphCalendarId = this.idCache.folders.get(folderId);
|
|
407
|
+
if (graphCalendarId == null) {
|
|
408
|
+
return this.listEventsAsync(limit);
|
|
409
|
+
}
|
|
410
|
+
const events = await this.client.listEvents(limit, graphCalendarId);
|
|
411
|
+
for (const event of events) {
|
|
412
|
+
if (event.id != null) {
|
|
413
|
+
const numericId = hashStringToNumber(event.id);
|
|
414
|
+
this.idCache.events.set(numericId, event.id);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return events.map((e) => mapEventToEventRow(e, graphCalendarId));
|
|
418
|
+
}
|
|
419
|
+
searchEvents(_query, _startDate, _endDate, _limit) {
|
|
420
|
+
throw new Error('Use searchEventsAsync() for Graph repository');
|
|
421
|
+
}
|
|
422
|
+
async searchEventsAsync(query, startDate, endDate, limit) {
|
|
423
|
+
// Graph doesn't have direct event search, so we filter client-side
|
|
424
|
+
const start = startDate != null ? new Date(startDate) : undefined;
|
|
425
|
+
const end = endDate != null ? new Date(endDate) : undefined;
|
|
426
|
+
const events = await this.client.listEvents(1000, undefined, start, end);
|
|
427
|
+
for (const event of events) {
|
|
428
|
+
if (event.id != null) {
|
|
429
|
+
const numericId = hashStringToNumber(event.id);
|
|
430
|
+
this.idCache.events.set(numericId, event.id);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
let rows = events.map((e) => mapEventToEventRow(e));
|
|
434
|
+
// Filter by title client-side if query provided
|
|
435
|
+
if (query != null) {
|
|
436
|
+
const queryLower = query.toLowerCase();
|
|
437
|
+
rows = rows.filter((row) => {
|
|
438
|
+
// EventRow doesn't have title, so we need to check the original event
|
|
439
|
+
const originalEvent = events.find((e) => e.id != null && hashStringToNumber(e.id) === row.id);
|
|
440
|
+
const subject = originalEvent?.subject?.toLowerCase() ?? '';
|
|
441
|
+
return subject.includes(queryLower);
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
return rows.slice(0, limit);
|
|
445
|
+
}
|
|
446
|
+
listEventsByDateRange(_startDate, _endDate, _limit) {
|
|
447
|
+
throw new Error('Use listEventsByDateRangeAsync() for Graph repository');
|
|
448
|
+
}
|
|
449
|
+
async listEventsByDateRangeAsync(startDate, endDate, limit) {
|
|
450
|
+
const start = new Date(startDate * 1000);
|
|
451
|
+
const end = new Date(endDate * 1000);
|
|
452
|
+
const events = await this.client.listEvents(limit, undefined, start, end);
|
|
453
|
+
for (const event of events) {
|
|
454
|
+
if (event.id != null) {
|
|
455
|
+
const numericId = hashStringToNumber(event.id);
|
|
456
|
+
this.idCache.events.set(numericId, event.id);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return events.map((e) => mapEventToEventRow(e));
|
|
460
|
+
}
|
|
461
|
+
getEvent(_id) {
|
|
462
|
+
throw new Error('Use getEventAsync() for Graph repository');
|
|
463
|
+
}
|
|
464
|
+
async getEventAsync(id) {
|
|
465
|
+
const graphId = this.idCache.events.get(id);
|
|
466
|
+
if (graphId == null) {
|
|
467
|
+
return undefined;
|
|
468
|
+
}
|
|
469
|
+
const event = await this.client.getEvent(graphId);
|
|
470
|
+
return event != null ? mapEventToEventRow(event) : undefined;
|
|
471
|
+
}
|
|
472
|
+
async listEventInstancesAsync(eventId, startDate, endDate) {
|
|
473
|
+
const graphId = this.idCache.events.get(eventId);
|
|
474
|
+
if (graphId == null) {
|
|
475
|
+
throw new Error(`Event ID ${eventId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
476
|
+
}
|
|
477
|
+
const instances = await this.client.listEventInstances(graphId, startDate, endDate);
|
|
478
|
+
for (const inst of instances) {
|
|
479
|
+
if (inst.id != null) {
|
|
480
|
+
this.idCache.events.set(hashStringToNumber(inst.id), inst.id);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return instances.map((e) => mapEventToEventRow(e));
|
|
484
|
+
}
|
|
485
|
+
// ===========================================================================
|
|
486
|
+
// Contacts
|
|
487
|
+
// ===========================================================================
|
|
488
|
+
listContacts(_limit, _offset) {
|
|
489
|
+
throw new Error('Use listContactsAsync() for Graph repository');
|
|
490
|
+
}
|
|
491
|
+
async listContactsAsync(limit, offset) {
|
|
492
|
+
const contacts = await this.client.listContacts(limit, offset);
|
|
493
|
+
for (const contact of contacts) {
|
|
494
|
+
if (contact.id != null) {
|
|
495
|
+
const numericId = hashStringToNumber(contact.id);
|
|
496
|
+
this.idCache.contacts.set(numericId, contact.id);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return contacts.map(mapContactToContactRow);
|
|
500
|
+
}
|
|
501
|
+
searchContacts(_query, _limit) {
|
|
502
|
+
throw new Error('Use searchContactsAsync() for Graph repository');
|
|
503
|
+
}
|
|
504
|
+
async searchContactsAsync(query, limit) {
|
|
505
|
+
const contacts = await this.client.searchContacts(query, limit);
|
|
506
|
+
for (const contact of contacts) {
|
|
507
|
+
if (contact.id != null) {
|
|
508
|
+
const numericId = hashStringToNumber(contact.id);
|
|
509
|
+
this.idCache.contacts.set(numericId, contact.id);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return contacts.map(mapContactToContactRow);
|
|
513
|
+
}
|
|
514
|
+
getContact(_id) {
|
|
515
|
+
throw new Error('Use getContactAsync() for Graph repository');
|
|
516
|
+
}
|
|
517
|
+
async getContactAsync(id) {
|
|
518
|
+
const graphId = this.idCache.contacts.get(id);
|
|
519
|
+
if (graphId == null) {
|
|
520
|
+
return undefined;
|
|
521
|
+
}
|
|
522
|
+
const contact = await this.client.getContact(graphId);
|
|
523
|
+
return contact != null ? mapContactToContactRow(contact) : undefined;
|
|
524
|
+
}
|
|
525
|
+
// ===========================================================================
|
|
526
|
+
// Contact Folders
|
|
527
|
+
// ===========================================================================
|
|
528
|
+
async listContactFoldersAsync() {
|
|
529
|
+
const folders = await this.client.listContactFolders();
|
|
530
|
+
return folders.map((folder) => {
|
|
531
|
+
const graphId = folder.id;
|
|
532
|
+
const numericId = hashStringToNumber(graphId);
|
|
533
|
+
this.idCache.contactFolders.set(numericId, graphId);
|
|
534
|
+
return {
|
|
535
|
+
id: numericId,
|
|
536
|
+
name: folder.displayName ?? '',
|
|
537
|
+
parentFolderId: folder.parentFolderId ?? null,
|
|
538
|
+
};
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
async createContactFolderAsync(name) {
|
|
542
|
+
const created = await this.client.createContactFolder(name);
|
|
543
|
+
const graphId = created.id;
|
|
544
|
+
const numericId = hashStringToNumber(graphId);
|
|
545
|
+
this.idCache.contactFolders.set(numericId, graphId);
|
|
546
|
+
return numericId;
|
|
547
|
+
}
|
|
548
|
+
async deleteContactFolderAsync(folderId) {
|
|
549
|
+
const graphId = this.idCache.contactFolders.get(folderId);
|
|
550
|
+
if (graphId == null)
|
|
551
|
+
throw new Error(`Contact folder ID ${folderId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
552
|
+
await this.client.deleteContactFolder(graphId);
|
|
553
|
+
this.idCache.contactFolders.delete(folderId);
|
|
554
|
+
}
|
|
555
|
+
async listContactsInFolderAsync(folderId, limit = 100) {
|
|
556
|
+
const graphId = this.idCache.contactFolders.get(folderId);
|
|
557
|
+
if (graphId == null)
|
|
558
|
+
throw new Error(`Contact folder ID ${folderId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
559
|
+
const contacts = await this.client.listContactsInFolder(graphId, limit);
|
|
560
|
+
return contacts.map((c) => {
|
|
561
|
+
if (c.id != null) {
|
|
562
|
+
this.idCache.contacts.set(hashStringToNumber(c.id), c.id);
|
|
563
|
+
}
|
|
564
|
+
return mapContactToContactRow(c);
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
// ===========================================================================
|
|
568
|
+
// Contact Photos
|
|
569
|
+
// ===========================================================================
|
|
570
|
+
async getContactPhotoAsync(contactId) {
|
|
571
|
+
const graphId = this.idCache.contacts.get(contactId);
|
|
572
|
+
if (graphId == null)
|
|
573
|
+
throw new Error(`Contact ID ${contactId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
574
|
+
const photoData = await this.client.getContactPhoto(graphId);
|
|
575
|
+
const downloadDir = getDownloadDir();
|
|
576
|
+
const filePath = path.join(downloadDir, `contact-${contactId}-photo.jpg`);
|
|
577
|
+
fs.writeFileSync(filePath, Buffer.from(photoData));
|
|
578
|
+
return { filePath, contentType: 'image/jpeg' };
|
|
579
|
+
}
|
|
580
|
+
async setContactPhotoAsync(contactId, filePath) {
|
|
581
|
+
const graphId = this.idCache.contacts.get(contactId);
|
|
582
|
+
if (graphId == null)
|
|
583
|
+
throw new Error(`Contact ID ${contactId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
584
|
+
const photoData = fs.readFileSync(filePath);
|
|
585
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
586
|
+
const contentType = ext === '.png' ? 'image/png' : 'image/jpeg';
|
|
587
|
+
await this.client.setContactPhoto(graphId, photoData, contentType);
|
|
588
|
+
}
|
|
589
|
+
// ===========================================================================
|
|
590
|
+
// Tasks
|
|
591
|
+
// ===========================================================================
|
|
592
|
+
listTasks(_limit, _offset) {
|
|
593
|
+
throw new Error('Use listTasksAsync() for Graph repository');
|
|
594
|
+
}
|
|
595
|
+
async listTasksAsync(limit, offset) {
|
|
596
|
+
const tasks = await this.client.listAllTasks(limit, offset, true);
|
|
597
|
+
for (const task of tasks) {
|
|
598
|
+
if (task.id != null && task.taskListId != null) {
|
|
599
|
+
const numericId = hashStringToNumber(task.id);
|
|
600
|
+
this.idCache.tasks.set(numericId, { taskListId: task.taskListId, taskId: task.id });
|
|
601
|
+
const listNumericId = hashStringToNumber(task.taskListId);
|
|
602
|
+
this.idCache.taskLists.set(listNumericId, task.taskListId);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return tasks.map(mapTaskToTaskRow);
|
|
606
|
+
}
|
|
607
|
+
listIncompleteTasks(_limit, _offset) {
|
|
608
|
+
throw new Error('Use listIncompleteTasksAsync() for Graph repository');
|
|
609
|
+
}
|
|
610
|
+
async listIncompleteTasksAsync(limit, offset) {
|
|
611
|
+
const tasks = await this.client.listAllTasks(limit, offset, false);
|
|
612
|
+
for (const task of tasks) {
|
|
613
|
+
if (task.id != null && task.taskListId != null) {
|
|
614
|
+
const numericId = hashStringToNumber(task.id);
|
|
615
|
+
this.idCache.tasks.set(numericId, { taskListId: task.taskListId, taskId: task.id });
|
|
616
|
+
const listNumericId = hashStringToNumber(task.taskListId);
|
|
617
|
+
this.idCache.taskLists.set(listNumericId, task.taskListId);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
return tasks.map(mapTaskToTaskRow);
|
|
621
|
+
}
|
|
622
|
+
searchTasks(_query, _limit) {
|
|
623
|
+
throw new Error('Use searchTasksAsync() for Graph repository');
|
|
624
|
+
}
|
|
625
|
+
async searchTasksAsync(query, limit) {
|
|
626
|
+
const tasks = await this.client.searchTasks(query, limit);
|
|
627
|
+
for (const task of tasks) {
|
|
628
|
+
if (task.id != null && task.taskListId != null) {
|
|
629
|
+
const numericId = hashStringToNumber(task.id);
|
|
630
|
+
this.idCache.tasks.set(numericId, { taskListId: task.taskListId, taskId: task.id });
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return tasks.map(mapTaskToTaskRow);
|
|
634
|
+
}
|
|
635
|
+
getTask(_id) {
|
|
636
|
+
throw new Error('Use getTaskAsync() for Graph repository');
|
|
637
|
+
}
|
|
638
|
+
async getTaskAsync(id) {
|
|
639
|
+
const taskInfo = this.idCache.tasks.get(id);
|
|
640
|
+
if (taskInfo == null) {
|
|
641
|
+
return undefined;
|
|
642
|
+
}
|
|
643
|
+
const task = await this.client.getTask(taskInfo.taskListId, taskInfo.taskId);
|
|
644
|
+
if (task == null) {
|
|
645
|
+
return undefined;
|
|
646
|
+
}
|
|
647
|
+
return mapTaskToTaskRow({ ...task, taskListId: taskInfo.taskListId });
|
|
648
|
+
}
|
|
649
|
+
async listTaskListsAsync() {
|
|
650
|
+
const lists = await this.client.listTaskLists();
|
|
651
|
+
return lists.map((list) => {
|
|
652
|
+
const graphId = list.id;
|
|
653
|
+
const numericId = hashStringToNumber(graphId);
|
|
654
|
+
this.idCache.taskLists.set(numericId, graphId);
|
|
655
|
+
return {
|
|
656
|
+
id: numericId,
|
|
657
|
+
name: list.displayName ?? '',
|
|
658
|
+
isDefault: list.wellknownListName === 'defaultList',
|
|
659
|
+
};
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
// ===========================================================================
|
|
663
|
+
// Notes (NOT SUPPORTED)
|
|
664
|
+
// ===========================================================================
|
|
665
|
+
listNotes(_limit, _offset) {
|
|
666
|
+
// Microsoft Graph does not have an API for Outlook Notes
|
|
667
|
+
return [];
|
|
668
|
+
}
|
|
669
|
+
listNotesAsync(_limit, _offset) {
|
|
670
|
+
// Microsoft Graph does not have an API for Outlook Notes
|
|
671
|
+
return Promise.resolve([]);
|
|
672
|
+
}
|
|
673
|
+
getNote(_id) {
|
|
674
|
+
// Microsoft Graph does not have an API for Outlook Notes
|
|
675
|
+
return undefined;
|
|
676
|
+
}
|
|
677
|
+
getNoteAsync(_id) {
|
|
678
|
+
// Microsoft Graph does not have an API for Outlook Notes
|
|
679
|
+
return Promise.resolve(undefined);
|
|
680
|
+
}
|
|
681
|
+
// ===========================================================================
|
|
682
|
+
// Utility Methods
|
|
683
|
+
// ===========================================================================
|
|
684
|
+
/**
|
|
685
|
+
* Gets the Graph client instance for direct access if needed.
|
|
686
|
+
*/
|
|
687
|
+
getClient() {
|
|
688
|
+
return this.client;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Returns the Graph client (satisfies IMailSendRepository).
|
|
692
|
+
*/
|
|
693
|
+
getGraphClient() {
|
|
694
|
+
return this.client;
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Returns the Graph string ID for a cached draft numeric ID (satisfies IMailSendRepository).
|
|
698
|
+
*/
|
|
699
|
+
getGraphIdForDraft(draftId) {
|
|
700
|
+
return this.idCache.messages.get(draftId);
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Gets the Graph string ID from a numeric ID.
|
|
704
|
+
*/
|
|
705
|
+
getGraphId(type, numericId) {
|
|
706
|
+
switch (type) {
|
|
707
|
+
case 'folder':
|
|
708
|
+
return this.idCache.folders.get(numericId);
|
|
709
|
+
case 'message':
|
|
710
|
+
return this.idCache.messages.get(numericId);
|
|
711
|
+
case 'event':
|
|
712
|
+
return this.idCache.events.get(numericId);
|
|
713
|
+
case 'contact':
|
|
714
|
+
return this.idCache.contacts.get(numericId);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Gets task info from a numeric ID.
|
|
719
|
+
*/
|
|
720
|
+
getTaskInfo(numericId) {
|
|
721
|
+
return this.idCache.tasks.get(numericId);
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Gets the Graph string ID for a task list from a numeric ID.
|
|
725
|
+
*/
|
|
726
|
+
getTaskListGraphId(numericId) {
|
|
727
|
+
return this.idCache.taskLists.get(numericId);
|
|
728
|
+
}
|
|
729
|
+
// ===========================================================================
|
|
730
|
+
// Write Operations (Async)
|
|
731
|
+
// ===========================================================================
|
|
732
|
+
// Sync versions throw — use async versions from index.ts handler
|
|
733
|
+
moveEmail(_emailId, _destinationFolderId) {
|
|
734
|
+
throw new Error('Use moveEmailAsync() for Graph repository');
|
|
735
|
+
}
|
|
736
|
+
deleteEmail(_emailId) {
|
|
737
|
+
throw new Error('Use deleteEmailAsync() for Graph repository');
|
|
738
|
+
}
|
|
739
|
+
archiveEmail(_emailId) {
|
|
740
|
+
throw new Error('Use archiveEmailAsync() for Graph repository');
|
|
741
|
+
}
|
|
742
|
+
junkEmail(_emailId) {
|
|
743
|
+
throw new Error('Use junkEmailAsync() for Graph repository');
|
|
744
|
+
}
|
|
745
|
+
markEmailRead(_emailId, _isRead) {
|
|
746
|
+
throw new Error('Use markEmailReadAsync() for Graph repository');
|
|
747
|
+
}
|
|
748
|
+
setEmailFlag(_emailId, _flagStatus) {
|
|
749
|
+
throw new Error('Use setEmailFlagAsync() for Graph repository');
|
|
750
|
+
}
|
|
751
|
+
setEmailCategories(_emailId, _categories) {
|
|
752
|
+
throw new Error('Use setEmailCategoriesAsync() for Graph repository');
|
|
753
|
+
}
|
|
754
|
+
setEmailImportance(_emailId, _importance) {
|
|
755
|
+
throw new Error('Use setEmailImportanceAsync() for Graph repository');
|
|
756
|
+
}
|
|
757
|
+
createFolder(_name, _parentFolderId) {
|
|
758
|
+
throw new Error('Use createFolderAsync() for Graph repository');
|
|
759
|
+
}
|
|
760
|
+
deleteFolder(_folderId) {
|
|
761
|
+
throw new Error('Use deleteFolderAsync() for Graph repository');
|
|
762
|
+
}
|
|
763
|
+
renameFolder(_folderId, _newName) {
|
|
764
|
+
throw new Error('Use renameFolderAsync() for Graph repository');
|
|
765
|
+
}
|
|
766
|
+
moveFolder(_folderId, _destinationParentId) {
|
|
767
|
+
throw new Error('Use moveFolderAsync() for Graph repository');
|
|
768
|
+
}
|
|
769
|
+
emptyFolder(_folderId) {
|
|
770
|
+
throw new Error('Use emptyFolderAsync() for Graph repository');
|
|
771
|
+
}
|
|
772
|
+
// Async implementations
|
|
773
|
+
async moveEmailAsync(emailId, destinationFolderId) {
|
|
774
|
+
const graphMessageId = this.idCache.messages.get(emailId);
|
|
775
|
+
const graphFolderId = this.idCache.folders.get(destinationFolderId);
|
|
776
|
+
if (graphMessageId == null)
|
|
777
|
+
throw new Error(`Message ID ${emailId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
778
|
+
if (graphFolderId == null)
|
|
779
|
+
throw new Error(`Folder ID ${destinationFolderId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
780
|
+
await this.client.moveMessage(graphMessageId, graphFolderId);
|
|
781
|
+
}
|
|
782
|
+
async deleteEmailAsync(emailId) {
|
|
783
|
+
const graphId = this.idCache.messages.get(emailId);
|
|
784
|
+
if (graphId == null)
|
|
785
|
+
throw new Error(`Message ID ${emailId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
786
|
+
await this.client.deleteMessage(graphId);
|
|
787
|
+
}
|
|
788
|
+
async archiveEmailAsync(emailId) {
|
|
789
|
+
const graphId = this.idCache.messages.get(emailId);
|
|
790
|
+
if (graphId == null)
|
|
791
|
+
throw new Error(`Message ID ${emailId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
792
|
+
await this.client.archiveMessage(graphId);
|
|
793
|
+
}
|
|
794
|
+
async junkEmailAsync(emailId) {
|
|
795
|
+
const graphId = this.idCache.messages.get(emailId);
|
|
796
|
+
if (graphId == null)
|
|
797
|
+
throw new Error(`Message ID ${emailId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
798
|
+
await this.client.junkMessage(graphId);
|
|
799
|
+
}
|
|
800
|
+
async markEmailReadAsync(emailId, isRead) {
|
|
801
|
+
const graphId = this.idCache.messages.get(emailId);
|
|
802
|
+
if (graphId == null)
|
|
803
|
+
throw new Error(`Message ID ${emailId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
804
|
+
await this.client.updateMessage(graphId, { isRead });
|
|
805
|
+
}
|
|
806
|
+
async setEmailFlagAsync(emailId, flagStatus) {
|
|
807
|
+
const graphId = this.idCache.messages.get(emailId);
|
|
808
|
+
if (graphId == null)
|
|
809
|
+
throw new Error(`Message ID ${emailId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
810
|
+
const flagStatusMap = {
|
|
811
|
+
0: 'notFlagged',
|
|
812
|
+
1: 'flagged',
|
|
813
|
+
2: 'complete',
|
|
814
|
+
};
|
|
815
|
+
await this.client.updateMessage(graphId, {
|
|
816
|
+
flag: { flagStatus: flagStatusMap[flagStatus] ?? 'notFlagged' },
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
async setEmailCategoriesAsync(emailId, categories) {
|
|
820
|
+
const graphId = this.idCache.messages.get(emailId);
|
|
821
|
+
if (graphId == null)
|
|
822
|
+
throw new Error(`Message ID ${emailId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
823
|
+
await this.client.updateMessage(graphId, { categories });
|
|
824
|
+
}
|
|
825
|
+
async setEmailImportanceAsync(emailId, importance) {
|
|
826
|
+
const graphId = this.idCache.messages.get(emailId);
|
|
827
|
+
if (graphId == null)
|
|
828
|
+
throw new Error(`Message ID ${emailId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
829
|
+
await this.client.updateMessage(graphId, { importance });
|
|
830
|
+
}
|
|
831
|
+
async createFolderAsync(name, parentFolderId) {
|
|
832
|
+
const graphParentId = parentFolderId != null
|
|
833
|
+
? this.idCache.folders.get(parentFolderId)
|
|
834
|
+
: undefined;
|
|
835
|
+
const folder = await this.client.createMailFolder(name, graphParentId ?? undefined);
|
|
836
|
+
// Update cache with new folder
|
|
837
|
+
if (folder.id != null) {
|
|
838
|
+
const numericId = hashStringToNumber(folder.id);
|
|
839
|
+
this.idCache.folders.set(numericId, folder.id);
|
|
840
|
+
}
|
|
841
|
+
return mapMailFolderToRow(folder);
|
|
842
|
+
}
|
|
843
|
+
async deleteFolderAsync(folderId) {
|
|
844
|
+
const graphId = this.idCache.folders.get(folderId);
|
|
845
|
+
if (graphId == null)
|
|
846
|
+
throw new Error(`Folder ID ${folderId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
847
|
+
await this.client.deleteMailFolder(graphId);
|
|
848
|
+
this.idCache.folders.delete(folderId);
|
|
849
|
+
}
|
|
850
|
+
async renameFolderAsync(folderId, newName) {
|
|
851
|
+
const graphId = this.idCache.folders.get(folderId);
|
|
852
|
+
if (graphId == null)
|
|
853
|
+
throw new Error(`Folder ID ${folderId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
854
|
+
await this.client.renameMailFolder(graphId, newName);
|
|
855
|
+
}
|
|
856
|
+
async moveFolderAsync(folderId, destinationParentId) {
|
|
857
|
+
const graphFolderId = this.idCache.folders.get(folderId);
|
|
858
|
+
const graphParentId = this.idCache.folders.get(destinationParentId);
|
|
859
|
+
if (graphFolderId == null)
|
|
860
|
+
throw new Error(`Folder ID ${folderId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
861
|
+
if (graphParentId == null)
|
|
862
|
+
throw new Error(`Parent folder ID ${destinationParentId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
863
|
+
await this.client.moveMailFolder(graphFolderId, graphParentId);
|
|
864
|
+
}
|
|
865
|
+
async emptyFolderAsync(folderId) {
|
|
866
|
+
const graphId = this.idCache.folders.get(folderId);
|
|
867
|
+
if (graphId == null)
|
|
868
|
+
throw new Error(`Folder ID ${folderId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
869
|
+
await this.client.emptyMailFolder(graphId);
|
|
870
|
+
}
|
|
871
|
+
// ===========================================================================
|
|
872
|
+
// Draft & Send Operations (Async)
|
|
873
|
+
// ===========================================================================
|
|
874
|
+
/**
|
|
875
|
+
* Creates a new draft message.
|
|
876
|
+
*
|
|
877
|
+
* Converts email address strings to Recipient objects, calls the Graph client,
|
|
878
|
+
* adds the returned draft to idCache.messages, and returns its numeric ID.
|
|
879
|
+
*/
|
|
880
|
+
async createDraftAsync(params) {
|
|
881
|
+
const toRecipients = (params.to ?? []).map(addr => ({
|
|
882
|
+
emailAddress: { address: addr },
|
|
883
|
+
}));
|
|
884
|
+
const ccRecipients = (params.cc ?? []).map(addr => ({
|
|
885
|
+
emailAddress: { address: addr },
|
|
886
|
+
}));
|
|
887
|
+
const bccRecipients = (params.bcc ?? []).map(addr => ({
|
|
888
|
+
emailAddress: { address: addr },
|
|
889
|
+
}));
|
|
890
|
+
const draft = await this.client.createDraft({
|
|
891
|
+
subject: params.subject,
|
|
892
|
+
body: { contentType: params.bodyType, content: params.body },
|
|
893
|
+
toRecipients,
|
|
894
|
+
ccRecipients,
|
|
895
|
+
bccRecipients,
|
|
896
|
+
});
|
|
897
|
+
const graphId = draft.id;
|
|
898
|
+
const numericId = hashStringToNumber(graphId);
|
|
899
|
+
this.idCache.messages.set(numericId, graphId);
|
|
900
|
+
return { numericId, graphId };
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Updates an existing draft message.
|
|
904
|
+
*
|
|
905
|
+
* Looks up the Graph string ID from idCache.messages, then calls the client.
|
|
906
|
+
*/
|
|
907
|
+
async updateDraftAsync(draftId, updates) {
|
|
908
|
+
const graphId = this.idCache.messages.get(draftId);
|
|
909
|
+
if (graphId == null)
|
|
910
|
+
throw new Error(`Message ID ${draftId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
911
|
+
await this.client.updateDraft(graphId, updates);
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* Lists draft messages.
|
|
915
|
+
*
|
|
916
|
+
* Uses the well-known 'drafts' folder name directly with the Graph API.
|
|
917
|
+
*/
|
|
918
|
+
async listDraftsAsync(limit, offset) {
|
|
919
|
+
return this.listEmailsWithGraphId('drafts', limit, offset);
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* Sends an existing draft message.
|
|
923
|
+
*
|
|
924
|
+
* Looks up the Graph string ID from idCache.messages, then calls the client.
|
|
925
|
+
*/
|
|
926
|
+
async sendDraftAsync(draftId) {
|
|
927
|
+
const graphId = this.idCache.messages.get(draftId);
|
|
928
|
+
if (graphId == null)
|
|
929
|
+
throw new Error(`Message ID ${draftId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
930
|
+
await this.client.sendDraft(graphId);
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* Sends a new email directly without creating a draft first.
|
|
934
|
+
*
|
|
935
|
+
* Converts email address strings to Recipient objects and calls the client.
|
|
936
|
+
*/
|
|
937
|
+
async sendMailAsync(params) {
|
|
938
|
+
const toRecipients = params.to.map(addr => ({
|
|
939
|
+
emailAddress: { address: addr },
|
|
940
|
+
}));
|
|
941
|
+
const ccRecipients = (params.cc ?? []).map(addr => ({
|
|
942
|
+
emailAddress: { address: addr },
|
|
943
|
+
}));
|
|
944
|
+
const bccRecipients = (params.bcc ?? []).map(addr => ({
|
|
945
|
+
emailAddress: { address: addr },
|
|
946
|
+
}));
|
|
947
|
+
await this.client.sendMail({
|
|
948
|
+
subject: params.subject,
|
|
949
|
+
body: { contentType: params.bodyType, content: params.body },
|
|
950
|
+
toRecipients,
|
|
951
|
+
ccRecipients,
|
|
952
|
+
bccRecipients,
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Replies to a message (or replies all).
|
|
957
|
+
*
|
|
958
|
+
* Looks up the Graph string ID from idCache.messages, then calls the client.
|
|
959
|
+
*/
|
|
960
|
+
async replyMessageAsync(messageId, comment, replyAll) {
|
|
961
|
+
const graphId = this.idCache.messages.get(messageId);
|
|
962
|
+
if (graphId == null)
|
|
963
|
+
throw new Error(`Message ID ${messageId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
964
|
+
await this.client.replyMessage(graphId, comment, replyAll);
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Forwards a message to specified recipients.
|
|
968
|
+
*
|
|
969
|
+
* Looks up the Graph string ID from idCache.messages, converts recipient
|
|
970
|
+
* email strings to Recipient objects, then calls the client.
|
|
971
|
+
*/
|
|
972
|
+
async forwardMessageAsync(messageId, toRecipients, comment) {
|
|
973
|
+
const graphId = this.idCache.messages.get(messageId);
|
|
974
|
+
if (graphId == null)
|
|
975
|
+
throw new Error(`Message ID ${messageId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
976
|
+
const recipients = toRecipients.map(addr => ({
|
|
977
|
+
emailAddress: { address: addr },
|
|
978
|
+
}));
|
|
979
|
+
await this.client.forwardMessage(graphId, recipients, comment);
|
|
980
|
+
}
|
|
981
|
+
// ===========================================================================
|
|
982
|
+
// Reply/Forward as Draft Operations (Async)
|
|
983
|
+
// ===========================================================================
|
|
984
|
+
/**
|
|
985
|
+
* Creates a reply (or reply-all) draft for a message.
|
|
986
|
+
*
|
|
987
|
+
* Looks up the Graph string ID from idCache.messages, creates the draft
|
|
988
|
+
* via the client, caches the new draft ID, and optionally updates the body.
|
|
989
|
+
*
|
|
990
|
+
* @returns The numeric and graph IDs of the new draft.
|
|
991
|
+
*/
|
|
992
|
+
async replyAsDraftAsync(messageId, replyAll = false, comment, bodyType = 'text') {
|
|
993
|
+
const graphMessageId = this.idCache.messages.get(messageId);
|
|
994
|
+
if (graphMessageId == null)
|
|
995
|
+
throw new Error(`Message ID ${messageId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
996
|
+
const draft = replyAll
|
|
997
|
+
? await this.client.createReplyAllDraft(graphMessageId)
|
|
998
|
+
: await this.client.createReplyDraft(graphMessageId);
|
|
999
|
+
const graphId = draft.id;
|
|
1000
|
+
const numericId = hashStringToNumber(graphId);
|
|
1001
|
+
this.idCache.messages.set(numericId, graphId);
|
|
1002
|
+
if (comment != null) {
|
|
1003
|
+
await this.client.updateDraft(graphId, {
|
|
1004
|
+
body: { contentType: bodyType, content: comment },
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
return { numericId, graphId };
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Creates a forward draft for a message.
|
|
1011
|
+
*
|
|
1012
|
+
* Looks up the Graph string ID from idCache.messages, creates the draft
|
|
1013
|
+
* via the client, caches the new draft ID, and optionally updates the
|
|
1014
|
+
* recipients and body.
|
|
1015
|
+
*
|
|
1016
|
+
* @returns The numeric and graph IDs of the new draft.
|
|
1017
|
+
*/
|
|
1018
|
+
async forwardAsDraftAsync(messageId, toRecipients, comment, bodyType = 'text') {
|
|
1019
|
+
const graphMessageId = this.idCache.messages.get(messageId);
|
|
1020
|
+
if (graphMessageId == null)
|
|
1021
|
+
throw new Error(`Message ID ${messageId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1022
|
+
const draft = await this.client.createForwardDraft(graphMessageId);
|
|
1023
|
+
const graphId = draft.id;
|
|
1024
|
+
const numericId = hashStringToNumber(graphId);
|
|
1025
|
+
this.idCache.messages.set(numericId, graphId);
|
|
1026
|
+
const updates = {};
|
|
1027
|
+
if (toRecipients != null && toRecipients.length > 0) {
|
|
1028
|
+
updates.toRecipients = toRecipients.map(addr => ({
|
|
1029
|
+
emailAddress: { address: addr },
|
|
1030
|
+
}));
|
|
1031
|
+
}
|
|
1032
|
+
if (comment != null) {
|
|
1033
|
+
updates.body = { contentType: bodyType, content: comment };
|
|
1034
|
+
}
|
|
1035
|
+
if (Object.keys(updates).length > 0) {
|
|
1036
|
+
await this.client.updateDraft(graphId, updates);
|
|
1037
|
+
}
|
|
1038
|
+
return { numericId, graphId };
|
|
1039
|
+
}
|
|
1040
|
+
// ---------------------------------------------------------------------------
|
|
1041
|
+
// Calendar Scheduling
|
|
1042
|
+
// ---------------------------------------------------------------------------
|
|
1043
|
+
async getScheduleAsync(params) {
|
|
1044
|
+
return await this.client.getSchedule({
|
|
1045
|
+
schedules: params.emailAddresses,
|
|
1046
|
+
startTime: { dateTime: params.startTime, timeZone: 'UTC' },
|
|
1047
|
+
endTime: { dateTime: params.endTime, timeZone: 'UTC' },
|
|
1048
|
+
availabilityViewInterval: params.availabilityViewInterval ?? 30,
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
async findMeetingTimesAsync(params) {
|
|
1052
|
+
const hours = Math.floor(params.durationMinutes / 60);
|
|
1053
|
+
const minutes = params.durationMinutes % 60;
|
|
1054
|
+
const meetingDuration = `PT${hours}H${minutes}M`;
|
|
1055
|
+
const attendees = params.attendees.map(addr => ({
|
|
1056
|
+
emailAddress: { address: addr },
|
|
1057
|
+
type: 'required',
|
|
1058
|
+
}));
|
|
1059
|
+
const request = {
|
|
1060
|
+
attendees,
|
|
1061
|
+
meetingDuration,
|
|
1062
|
+
maxCandidates: params.maxCandidates ?? 5,
|
|
1063
|
+
};
|
|
1064
|
+
if (params.startTime != null && params.endTime != null) {
|
|
1065
|
+
request.timeConstraint = {
|
|
1066
|
+
timeslots: [{
|
|
1067
|
+
start: { dateTime: params.startTime, timeZone: 'UTC' },
|
|
1068
|
+
end: { dateTime: params.endTime, timeZone: 'UTC' },
|
|
1069
|
+
}],
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
return await this.client.findMeetingTimes(request);
|
|
1073
|
+
}
|
|
1074
|
+
// ===========================================================================
|
|
1075
|
+
// Attachment Operations (Async)
|
|
1076
|
+
// ===========================================================================
|
|
1077
|
+
/**
|
|
1078
|
+
* Lists attachments for a given email.
|
|
1079
|
+
*
|
|
1080
|
+
* Looks up the Graph message ID from idCache.messages, calls
|
|
1081
|
+
* client.listAttachments, hashes each attachment ID to a numeric key,
|
|
1082
|
+
* and caches it in idCache.attachments with { messageId, attachmentId }.
|
|
1083
|
+
*
|
|
1084
|
+
* @returns Array of attachment metadata objects.
|
|
1085
|
+
*/
|
|
1086
|
+
async listAttachmentsAsync(emailId) {
|
|
1087
|
+
const graphMessageId = this.idCache.messages.get(emailId);
|
|
1088
|
+
if (graphMessageId == null)
|
|
1089
|
+
throw new Error(`Message ID ${emailId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1090
|
+
const attachments = await this.client.listAttachments(graphMessageId);
|
|
1091
|
+
return attachments.map((att) => {
|
|
1092
|
+
const attId = att.id ?? '';
|
|
1093
|
+
const numericId = hashStringToNumber(attId);
|
|
1094
|
+
this.idCache.attachments.set(numericId, {
|
|
1095
|
+
messageId: graphMessageId,
|
|
1096
|
+
attachmentId: attId,
|
|
1097
|
+
});
|
|
1098
|
+
return {
|
|
1099
|
+
id: numericId,
|
|
1100
|
+
name: att.name ?? '',
|
|
1101
|
+
size: att.size ?? 0,
|
|
1102
|
+
contentType: att.contentType ?? 'application/octet-stream',
|
|
1103
|
+
isInline: att.isInline ?? false,
|
|
1104
|
+
};
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Downloads an attachment for a given email.
|
|
1109
|
+
*
|
|
1110
|
+
* Looks up { messageId, attachmentId } from idCache.attachments,
|
|
1111
|
+
* then delegates to the downloadAttachment helper which fetches
|
|
1112
|
+
* the content and writes it to disk.
|
|
1113
|
+
*
|
|
1114
|
+
* @returns Metadata about the downloaded file including its local path.
|
|
1115
|
+
*/
|
|
1116
|
+
async downloadAttachmentAsync(attachmentId) {
|
|
1117
|
+
const cached = this.idCache.attachments.get(attachmentId);
|
|
1118
|
+
if (cached == null)
|
|
1119
|
+
throw new Error(`Attachment ID ${attachmentId} not found in cache. Call list_attachments first.`);
|
|
1120
|
+
return downloadAttachment(this.client, cached.messageId, cached.attachmentId);
|
|
1121
|
+
}
|
|
1122
|
+
// ===========================================================================
|
|
1123
|
+
// Calendar Write Operations (Async)
|
|
1124
|
+
// ===========================================================================
|
|
1125
|
+
/**
|
|
1126
|
+
* Creates a new calendar event.
|
|
1127
|
+
*
|
|
1128
|
+
* Builds a Graph API event object from the given params, calls
|
|
1129
|
+
* client.createEvent(), adds the result to idCache.events, and
|
|
1130
|
+
* returns the numeric ID.
|
|
1131
|
+
*/
|
|
1132
|
+
async createEventAsync(params) {
|
|
1133
|
+
const tz = params.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1134
|
+
const graphEvent = {
|
|
1135
|
+
subject: params.subject,
|
|
1136
|
+
start: { dateTime: params.start, timeZone: tz },
|
|
1137
|
+
end: { dateTime: params.end, timeZone: tz },
|
|
1138
|
+
};
|
|
1139
|
+
if (params.isAllDay === true) {
|
|
1140
|
+
graphEvent.isAllDay = true;
|
|
1141
|
+
}
|
|
1142
|
+
if (params.location != null) {
|
|
1143
|
+
graphEvent.location = { displayName: params.location };
|
|
1144
|
+
}
|
|
1145
|
+
if (params.body != null) {
|
|
1146
|
+
graphEvent.body = {
|
|
1147
|
+
contentType: params.bodyType ?? 'text',
|
|
1148
|
+
content: params.body,
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
if (params.attendees != null && params.attendees.length > 0) {
|
|
1152
|
+
graphEvent.attendees = params.attendees.map((a) => ({
|
|
1153
|
+
emailAddress: { address: a.email, name: a.name },
|
|
1154
|
+
type: a.type ?? 'required',
|
|
1155
|
+
}));
|
|
1156
|
+
}
|
|
1157
|
+
if (params.recurrence != null) {
|
|
1158
|
+
graphEvent.recurrence = params.recurrence;
|
|
1159
|
+
}
|
|
1160
|
+
if (params.is_online_meeting === true) {
|
|
1161
|
+
graphEvent.isOnlineMeeting = true;
|
|
1162
|
+
graphEvent.onlineMeetingProvider = params.online_meeting_provider ?? 'teamsForBusiness';
|
|
1163
|
+
}
|
|
1164
|
+
const graphCalendarId = params.calendarId != null
|
|
1165
|
+
? this.idCache.folders.get(params.calendarId)
|
|
1166
|
+
: undefined;
|
|
1167
|
+
const created = await this.client.createEvent(graphEvent, graphCalendarId);
|
|
1168
|
+
const graphId = created.id;
|
|
1169
|
+
const numericId = hashStringToNumber(graphId);
|
|
1170
|
+
this.idCache.events.set(numericId, graphId);
|
|
1171
|
+
return numericId;
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Updates an existing calendar event.
|
|
1175
|
+
*
|
|
1176
|
+
* Looks up the Graph string ID from idCache.events, then calls
|
|
1177
|
+
* client.updateEvent(). Throws if the event is not cached.
|
|
1178
|
+
*/
|
|
1179
|
+
async updateEventAsync(eventId, updates) {
|
|
1180
|
+
const graphId = this.idCache.events.get(eventId);
|
|
1181
|
+
if (graphId == null)
|
|
1182
|
+
throw new Error(`Event ID ${eventId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1183
|
+
await this.client.updateEvent(graphId, updates);
|
|
1184
|
+
}
|
|
1185
|
+
/**
|
|
1186
|
+
* Deletes a calendar event.
|
|
1187
|
+
*
|
|
1188
|
+
* Looks up the Graph string ID from idCache.events, calls
|
|
1189
|
+
* client.deleteEvent(), and removes the entry from idCache.
|
|
1190
|
+
* Throws if the event is not cached.
|
|
1191
|
+
*/
|
|
1192
|
+
async deleteEventAsync(eventId) {
|
|
1193
|
+
const graphId = this.idCache.events.get(eventId);
|
|
1194
|
+
if (graphId == null)
|
|
1195
|
+
throw new Error(`Event ID ${eventId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1196
|
+
await this.client.deleteEvent(graphId);
|
|
1197
|
+
this.idCache.events.delete(eventId);
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* Responds to a calendar event invitation.
|
|
1201
|
+
*
|
|
1202
|
+
* Looks up the Graph string ID from idCache.events, then calls
|
|
1203
|
+
* client.respondToEvent(). Throws if the event is not cached.
|
|
1204
|
+
*/
|
|
1205
|
+
async respondToEventAsync(eventId, response, sendResponse, comment) {
|
|
1206
|
+
const graphId = this.idCache.events.get(eventId);
|
|
1207
|
+
if (graphId == null)
|
|
1208
|
+
throw new Error(`Event ID ${eventId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1209
|
+
await this.client.respondToEvent(graphId, response, sendResponse, comment);
|
|
1210
|
+
}
|
|
1211
|
+
// ===========================================================================
|
|
1212
|
+
// Contact Write Operations (Async)
|
|
1213
|
+
// ===========================================================================
|
|
1214
|
+
/**
|
|
1215
|
+
* Creates a new contact.
|
|
1216
|
+
*
|
|
1217
|
+
* Maps snake_case input fields to Graph API camelCase fields, calls
|
|
1218
|
+
* client.createContact(), caches the resulting ID, and returns a numeric ID.
|
|
1219
|
+
*/
|
|
1220
|
+
async createContactAsync(params) {
|
|
1221
|
+
const graphContact = {};
|
|
1222
|
+
if (params.given_name != null)
|
|
1223
|
+
graphContact.givenName = params.given_name;
|
|
1224
|
+
if (params.surname != null)
|
|
1225
|
+
graphContact.surname = params.surname;
|
|
1226
|
+
if (params.email != null)
|
|
1227
|
+
graphContact.emailAddresses = [{ address: params.email }];
|
|
1228
|
+
if (params.phone != null)
|
|
1229
|
+
graphContact.businessPhones = [params.phone];
|
|
1230
|
+
if (params.mobile_phone != null)
|
|
1231
|
+
graphContact.mobilePhone = params.mobile_phone;
|
|
1232
|
+
if (params.company != null)
|
|
1233
|
+
graphContact.companyName = params.company;
|
|
1234
|
+
if (params.job_title != null)
|
|
1235
|
+
graphContact.jobTitle = params.job_title;
|
|
1236
|
+
// Build address only if any address field is present
|
|
1237
|
+
if (params.street_address != null || params.city != null || params.state != null || params.postal_code != null || params.country != null) {
|
|
1238
|
+
const address = {};
|
|
1239
|
+
if (params.street_address != null)
|
|
1240
|
+
address.street = params.street_address;
|
|
1241
|
+
if (params.city != null)
|
|
1242
|
+
address.city = params.city;
|
|
1243
|
+
if (params.state != null)
|
|
1244
|
+
address.state = params.state;
|
|
1245
|
+
if (params.postal_code != null)
|
|
1246
|
+
address.postalCode = params.postal_code;
|
|
1247
|
+
if (params.country != null)
|
|
1248
|
+
address.countryOrRegion = params.country;
|
|
1249
|
+
graphContact.businessAddress = address;
|
|
1250
|
+
}
|
|
1251
|
+
const created = await this.client.createContact(graphContact);
|
|
1252
|
+
const graphId = created.id;
|
|
1253
|
+
const numericId = hashStringToNumber(graphId);
|
|
1254
|
+
this.idCache.contacts.set(numericId, graphId);
|
|
1255
|
+
return numericId;
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* Updates an existing contact.
|
|
1259
|
+
*
|
|
1260
|
+
* Looks up the Graph string ID from idCache.contacts, then calls
|
|
1261
|
+
* client.updateContact(). Throws if the contact is not cached.
|
|
1262
|
+
*/
|
|
1263
|
+
async updateContactAsync(contactId, updates) {
|
|
1264
|
+
const graphId = this.idCache.contacts.get(contactId);
|
|
1265
|
+
if (graphId == null)
|
|
1266
|
+
throw new Error(`Contact ID ${contactId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1267
|
+
await this.client.updateContact(graphId, updates);
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Deletes a contact.
|
|
1271
|
+
*
|
|
1272
|
+
* Looks up the Graph string ID from idCache.contacts, calls
|
|
1273
|
+
* client.deleteContact(), and removes the entry from idCache.
|
|
1274
|
+
* Throws if the contact is not cached.
|
|
1275
|
+
*/
|
|
1276
|
+
async deleteContactAsync(contactId) {
|
|
1277
|
+
const graphId = this.idCache.contacts.get(contactId);
|
|
1278
|
+
if (graphId == null)
|
|
1279
|
+
throw new Error(`Contact ID ${contactId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1280
|
+
await this.client.deleteContact(graphId);
|
|
1281
|
+
this.idCache.contacts.delete(contactId);
|
|
1282
|
+
}
|
|
1283
|
+
// ===========================================================================
|
|
1284
|
+
// Task Write Operations (Async)
|
|
1285
|
+
// ===========================================================================
|
|
1286
|
+
/**
|
|
1287
|
+
* Creates a new task in a task list.
|
|
1288
|
+
*
|
|
1289
|
+
* Looks up the Graph task list ID from idCache.taskLists, builds a
|
|
1290
|
+
* Graph API task object from the given params, calls client.createTask(),
|
|
1291
|
+
* caches the resulting ID, and returns a numeric ID.
|
|
1292
|
+
*/
|
|
1293
|
+
async createTaskAsync(params) {
|
|
1294
|
+
const graphListId = this.idCache.taskLists.get(params.task_list_id);
|
|
1295
|
+
if (graphListId == null)
|
|
1296
|
+
throw new Error(`Task list ID ${params.task_list_id} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1297
|
+
const graphTask = {
|
|
1298
|
+
title: params.title,
|
|
1299
|
+
};
|
|
1300
|
+
if (params.body != null) {
|
|
1301
|
+
graphTask.body = {
|
|
1302
|
+
contentType: params.body_type ?? 'text',
|
|
1303
|
+
content: params.body,
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
if (params.due_date != null) {
|
|
1307
|
+
graphTask.dueDateTime = {
|
|
1308
|
+
dateTime: params.due_date,
|
|
1309
|
+
timeZone: 'UTC',
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
if (params.importance != null) {
|
|
1313
|
+
graphTask.importance = params.importance;
|
|
1314
|
+
}
|
|
1315
|
+
if (params.reminder_date != null) {
|
|
1316
|
+
graphTask.isReminderOn = true;
|
|
1317
|
+
graphTask.reminderDateTime = {
|
|
1318
|
+
dateTime: params.reminder_date,
|
|
1319
|
+
timeZone: 'UTC',
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
if (params.categories != null) {
|
|
1323
|
+
graphTask.categories = params.categories;
|
|
1324
|
+
}
|
|
1325
|
+
if (params.recurrence != null) {
|
|
1326
|
+
graphTask.recurrence = {
|
|
1327
|
+
pattern: {
|
|
1328
|
+
type: params.recurrence.pattern,
|
|
1329
|
+
interval: params.recurrence.interval ?? 1,
|
|
1330
|
+
...(params.recurrence.days_of_week != null ? { daysOfWeek: params.recurrence.days_of_week } : {}),
|
|
1331
|
+
...(params.recurrence.day_of_month != null ? { dayOfMonth: params.recurrence.day_of_month } : {}),
|
|
1332
|
+
},
|
|
1333
|
+
range: {
|
|
1334
|
+
type: params.recurrence.range_type,
|
|
1335
|
+
startDate: params.recurrence.start_date,
|
|
1336
|
+
...(params.recurrence.end_date != null ? { endDate: params.recurrence.end_date } : {}),
|
|
1337
|
+
...(params.recurrence.occurrences != null ? { numberOfOccurrences: params.recurrence.occurrences } : {}),
|
|
1338
|
+
},
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
const created = await this.client.createTask(graphListId, graphTask);
|
|
1342
|
+
const graphId = created.id;
|
|
1343
|
+
const numericId = hashStringToNumber(graphId);
|
|
1344
|
+
this.idCache.tasks.set(numericId, { taskListId: graphListId, taskId: graphId });
|
|
1345
|
+
return numericId;
|
|
1346
|
+
}
|
|
1347
|
+
/**
|
|
1348
|
+
* Updates an existing task.
|
|
1349
|
+
*
|
|
1350
|
+
* Looks up the Graph task info from idCache.tasks, then calls
|
|
1351
|
+
* client.updateTask(). Throws if the task is not cached.
|
|
1352
|
+
*/
|
|
1353
|
+
async updateTaskAsync(taskId, updates) {
|
|
1354
|
+
const taskInfo = this.idCache.tasks.get(taskId);
|
|
1355
|
+
if (taskInfo == null)
|
|
1356
|
+
throw new Error(`Task ID ${taskId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1357
|
+
await this.client.updateTask(taskInfo.taskListId, taskInfo.taskId, updates);
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Marks a task as completed.
|
|
1361
|
+
*
|
|
1362
|
+
* Convenience method that calls updateTaskAsync with status: 'completed'
|
|
1363
|
+
* and the current time as completedDateTime.
|
|
1364
|
+
*/
|
|
1365
|
+
async completeTaskAsync(taskId) {
|
|
1366
|
+
await this.updateTaskAsync(taskId, {
|
|
1367
|
+
status: 'completed',
|
|
1368
|
+
completedDateTime: {
|
|
1369
|
+
dateTime: new Date().toISOString(),
|
|
1370
|
+
timeZone: 'UTC',
|
|
1371
|
+
},
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Deletes a task.
|
|
1376
|
+
*
|
|
1377
|
+
* Looks up the Graph task info from idCache.tasks, calls
|
|
1378
|
+
* client.deleteTask(), and removes the entry from idCache.
|
|
1379
|
+
* Throws if the task is not cached.
|
|
1380
|
+
*/
|
|
1381
|
+
async deleteTaskAsync(taskId) {
|
|
1382
|
+
const taskInfo = this.idCache.tasks.get(taskId);
|
|
1383
|
+
if (taskInfo == null)
|
|
1384
|
+
throw new Error(`Task ID ${taskId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1385
|
+
await this.client.deleteTask(taskInfo.taskListId, taskInfo.taskId);
|
|
1386
|
+
this.idCache.tasks.delete(taskId);
|
|
1387
|
+
}
|
|
1388
|
+
/**
|
|
1389
|
+
* Creates a new task list.
|
|
1390
|
+
*
|
|
1391
|
+
* Calls client.createTaskList(), caches the resulting ID in
|
|
1392
|
+
* idCache.taskLists, and returns a numeric ID.
|
|
1393
|
+
*/
|
|
1394
|
+
async createTaskListAsync(displayName) {
|
|
1395
|
+
const created = await this.client.createTaskList(displayName);
|
|
1396
|
+
const graphId = created.id;
|
|
1397
|
+
const numericId = hashStringToNumber(graphId);
|
|
1398
|
+
this.idCache.taskLists.set(numericId, graphId);
|
|
1399
|
+
return numericId;
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Renames a task list.
|
|
1403
|
+
*/
|
|
1404
|
+
async renameTaskListAsync(listId, name) {
|
|
1405
|
+
const graphId = this.idCache.taskLists.get(listId);
|
|
1406
|
+
if (graphId == null)
|
|
1407
|
+
throw new Error(`Task list ID ${listId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1408
|
+
await this.client.updateTaskList(graphId, { displayName: name });
|
|
1409
|
+
}
|
|
1410
|
+
/**
|
|
1411
|
+
* Deletes a task list.
|
|
1412
|
+
*/
|
|
1413
|
+
async deleteTaskListAsync(listId) {
|
|
1414
|
+
const graphId = this.idCache.taskLists.get(listId);
|
|
1415
|
+
if (graphId == null)
|
|
1416
|
+
throw new Error(`Task list ID ${listId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1417
|
+
await this.client.deleteTaskList(graphId);
|
|
1418
|
+
this.idCache.taskLists.delete(listId);
|
|
1419
|
+
}
|
|
1420
|
+
// ===========================================================================
|
|
1421
|
+
// Checklist Items
|
|
1422
|
+
// ===========================================================================
|
|
1423
|
+
async listChecklistItemsAsync(taskId) {
|
|
1424
|
+
const taskInfo = this.idCache.tasks.get(taskId);
|
|
1425
|
+
if (taskInfo == null)
|
|
1426
|
+
throw new Error(`Task ID ${taskId} not found in cache. Try listing tasks first.`);
|
|
1427
|
+
const items = await this.client.listChecklistItems(taskInfo.taskListId, taskInfo.taskId);
|
|
1428
|
+
return items.map((item) => {
|
|
1429
|
+
const graphId = item.id;
|
|
1430
|
+
const numericId = hashStringToNumber(graphId);
|
|
1431
|
+
this.idCache.checklistItems.set(numericId, { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, checklistItemId: graphId });
|
|
1432
|
+
return {
|
|
1433
|
+
id: numericId,
|
|
1434
|
+
displayName: item.displayName ?? '',
|
|
1435
|
+
isChecked: item.isChecked ?? false,
|
|
1436
|
+
createdDateTime: item.createdDateTime ?? '',
|
|
1437
|
+
};
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1440
|
+
async createChecklistItemAsync(taskId, displayName, isChecked = false) {
|
|
1441
|
+
const taskInfo = this.idCache.tasks.get(taskId);
|
|
1442
|
+
if (taskInfo == null)
|
|
1443
|
+
throw new Error(`Task ID ${taskId} not found in cache. Try listing tasks first.`);
|
|
1444
|
+
const item = await this.client.createChecklistItem(taskInfo.taskListId, taskInfo.taskId, displayName, isChecked);
|
|
1445
|
+
const graphId = item.id;
|
|
1446
|
+
const numericId = hashStringToNumber(graphId);
|
|
1447
|
+
this.idCache.checklistItems.set(numericId, { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, checklistItemId: graphId });
|
|
1448
|
+
return numericId;
|
|
1449
|
+
}
|
|
1450
|
+
async updateChecklistItemAsync(checklistItemId, updates) {
|
|
1451
|
+
const cached = this.idCache.checklistItems.get(checklistItemId);
|
|
1452
|
+
if (cached == null)
|
|
1453
|
+
throw new Error(`Checklist item ID ${checklistItemId} not found in cache. Try listing checklist items first.`);
|
|
1454
|
+
const graphUpdates = {};
|
|
1455
|
+
if (updates.displayName != null)
|
|
1456
|
+
graphUpdates['displayName'] = updates.displayName;
|
|
1457
|
+
if (updates.isChecked != null)
|
|
1458
|
+
graphUpdates['isChecked'] = updates.isChecked;
|
|
1459
|
+
await this.client.updateChecklistItem(cached.taskListId, cached.taskId, cached.checklistItemId, graphUpdates);
|
|
1460
|
+
}
|
|
1461
|
+
async deleteChecklistItemAsync(checklistItemId) {
|
|
1462
|
+
const cached = this.idCache.checklistItems.get(checklistItemId);
|
|
1463
|
+
if (cached == null)
|
|
1464
|
+
throw new Error(`Checklist item ID ${checklistItemId} not found in cache. Try listing checklist items first.`);
|
|
1465
|
+
await this.client.deleteChecklistItem(cached.taskListId, cached.taskId, cached.checklistItemId);
|
|
1466
|
+
this.idCache.checklistItems.delete(checklistItemId);
|
|
1467
|
+
}
|
|
1468
|
+
// ===========================================================================
|
|
1469
|
+
// Linked Resources
|
|
1470
|
+
// ===========================================================================
|
|
1471
|
+
async listLinkedResourcesAsync(taskId) {
|
|
1472
|
+
const taskInfo = this.idCache.tasks.get(taskId);
|
|
1473
|
+
if (taskInfo == null)
|
|
1474
|
+
throw new Error(`Task ID ${taskId} not found in cache. Try listing tasks first.`);
|
|
1475
|
+
const items = await this.client.listLinkedResources(taskInfo.taskListId, taskInfo.taskId);
|
|
1476
|
+
return items.map((item) => {
|
|
1477
|
+
const graphId = item.id;
|
|
1478
|
+
const numericId = hashStringToNumber(graphId);
|
|
1479
|
+
this.idCache.linkedResources.set(numericId, { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, linkedResourceId: graphId });
|
|
1480
|
+
return {
|
|
1481
|
+
id: numericId,
|
|
1482
|
+
webUrl: item.webUrl ?? '',
|
|
1483
|
+
applicationName: item.applicationName ?? '',
|
|
1484
|
+
displayName: item.displayName ?? '',
|
|
1485
|
+
};
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
async createLinkedResourceAsync(taskId, webUrl, applicationName, displayName) {
|
|
1489
|
+
const taskInfo = this.idCache.tasks.get(taskId);
|
|
1490
|
+
if (taskInfo == null)
|
|
1491
|
+
throw new Error(`Task ID ${taskId} not found in cache. Try listing tasks first.`);
|
|
1492
|
+
const item = await this.client.createLinkedResource(taskInfo.taskListId, taskInfo.taskId, webUrl, applicationName, displayName);
|
|
1493
|
+
const graphId = item.id;
|
|
1494
|
+
const numericId = hashStringToNumber(graphId);
|
|
1495
|
+
this.idCache.linkedResources.set(numericId, { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, linkedResourceId: graphId });
|
|
1496
|
+
return numericId;
|
|
1497
|
+
}
|
|
1498
|
+
async deleteLinkedResourceAsync(linkedResourceId) {
|
|
1499
|
+
const cached = this.idCache.linkedResources.get(linkedResourceId);
|
|
1500
|
+
if (cached == null)
|
|
1501
|
+
throw new Error(`Linked resource ID ${linkedResourceId} not found in cache. Try listing linked resources first.`);
|
|
1502
|
+
await this.client.deleteLinkedResource(cached.taskListId, cached.taskId, cached.linkedResourceId);
|
|
1503
|
+
this.idCache.linkedResources.delete(linkedResourceId);
|
|
1504
|
+
}
|
|
1505
|
+
// ===========================================================================
|
|
1506
|
+
// Task Attachments
|
|
1507
|
+
// ===========================================================================
|
|
1508
|
+
async listTaskAttachmentsAsync(taskId) {
|
|
1509
|
+
const taskInfo = this.idCache.tasks.get(taskId);
|
|
1510
|
+
if (taskInfo == null)
|
|
1511
|
+
throw new Error(`Task ID ${taskId} not found in cache. Try listing tasks first.`);
|
|
1512
|
+
const items = await this.client.listTaskAttachments(taskInfo.taskListId, taskInfo.taskId);
|
|
1513
|
+
return items.map((item) => {
|
|
1514
|
+
const graphId = item.id;
|
|
1515
|
+
const numericId = hashStringToNumber(graphId);
|
|
1516
|
+
this.idCache.taskAttachments.set(numericId, { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, attachmentId: graphId });
|
|
1517
|
+
return {
|
|
1518
|
+
id: numericId,
|
|
1519
|
+
name: item['name'] ?? '',
|
|
1520
|
+
size: item.size ?? 0,
|
|
1521
|
+
contentType: item.contentType ?? '',
|
|
1522
|
+
};
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
async createTaskAttachmentAsync(taskId, name, contentBytes, contentType) {
|
|
1526
|
+
const taskInfo = this.idCache.tasks.get(taskId);
|
|
1527
|
+
if (taskInfo == null)
|
|
1528
|
+
throw new Error(`Task ID ${taskId} not found in cache. Try listing tasks first.`);
|
|
1529
|
+
const item = await this.client.createTaskAttachment(taskInfo.taskListId, taskInfo.taskId, name, contentBytes, contentType);
|
|
1530
|
+
const graphId = item.id;
|
|
1531
|
+
const numericId = hashStringToNumber(graphId);
|
|
1532
|
+
this.idCache.taskAttachments.set(numericId, { taskListId: taskInfo.taskListId, taskId: taskInfo.taskId, attachmentId: graphId });
|
|
1533
|
+
return numericId;
|
|
1534
|
+
}
|
|
1535
|
+
async deleteTaskAttachmentAsync(taskAttachmentId) {
|
|
1536
|
+
const cached = this.idCache.taskAttachments.get(taskAttachmentId);
|
|
1537
|
+
if (cached == null)
|
|
1538
|
+
throw new Error(`Task attachment ID ${taskAttachmentId} not found in cache. Try listing task attachments first.`);
|
|
1539
|
+
await this.client.deleteTaskAttachment(cached.taskListId, cached.taskId, cached.attachmentId);
|
|
1540
|
+
this.idCache.taskAttachments.delete(taskAttachmentId);
|
|
1541
|
+
}
|
|
1542
|
+
// ===========================================================================
|
|
1543
|
+
// Mail Rules (Async)
|
|
1544
|
+
// ===========================================================================
|
|
1545
|
+
/**
|
|
1546
|
+
* Lists all inbox mail rules.
|
|
1547
|
+
*/
|
|
1548
|
+
async listMailRulesAsync() {
|
|
1549
|
+
const rules = await this.client.listMailRules();
|
|
1550
|
+
return rules.map((rule) => {
|
|
1551
|
+
const graphId = rule.id;
|
|
1552
|
+
const numericId = hashStringToNumber(graphId);
|
|
1553
|
+
this.idCache.rules.set(numericId, graphId);
|
|
1554
|
+
return {
|
|
1555
|
+
id: numericId,
|
|
1556
|
+
displayName: rule.displayName ?? '',
|
|
1557
|
+
sequence: rule.sequence ?? 0,
|
|
1558
|
+
isEnabled: rule.isEnabled ?? true,
|
|
1559
|
+
conditions: rule.conditions ?? {},
|
|
1560
|
+
actions: rule.actions ?? {},
|
|
1561
|
+
};
|
|
1562
|
+
});
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* Creates a new inbox mail rule.
|
|
1566
|
+
*/
|
|
1567
|
+
async createMailRuleAsync(rule) {
|
|
1568
|
+
const created = await this.client.createMailRule(rule);
|
|
1569
|
+
const graphId = created.id;
|
|
1570
|
+
const numericId = hashStringToNumber(graphId);
|
|
1571
|
+
this.idCache.rules.set(numericId, graphId);
|
|
1572
|
+
return numericId;
|
|
1573
|
+
}
|
|
1574
|
+
/**
|
|
1575
|
+
* Deletes an inbox mail rule.
|
|
1576
|
+
*/
|
|
1577
|
+
async deleteMailRuleAsync(ruleId) {
|
|
1578
|
+
const graphId = this.idCache.rules.get(ruleId);
|
|
1579
|
+
if (graphId == null)
|
|
1580
|
+
throw new Error(`Rule ID ${ruleId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1581
|
+
await this.client.deleteMailRule(graphId);
|
|
1582
|
+
this.idCache.rules.delete(ruleId);
|
|
1583
|
+
}
|
|
1584
|
+
// ===========================================================================
|
|
1585
|
+
// Automatic Replies (Out of Office)
|
|
1586
|
+
// ===========================================================================
|
|
1587
|
+
/**
|
|
1588
|
+
* Gets the current automatic replies (OOF) settings.
|
|
1589
|
+
*/
|
|
1590
|
+
async getAutomaticRepliesAsync() {
|
|
1591
|
+
const settings = await this.client.getAutomaticReplies();
|
|
1592
|
+
const startDt = settings.scheduledStartDateTime;
|
|
1593
|
+
const endDt = settings.scheduledEndDateTime;
|
|
1594
|
+
return {
|
|
1595
|
+
status: settings.status ?? 'disabled',
|
|
1596
|
+
externalAudience: settings.externalAudience ?? 'none',
|
|
1597
|
+
internalReplyMessage: settings.internalReplyMessage ?? '',
|
|
1598
|
+
externalReplyMessage: settings.externalReplyMessage ?? '',
|
|
1599
|
+
scheduledStartDateTime: startDt?.dateTime ?? null,
|
|
1600
|
+
scheduledEndDateTime: endDt?.dateTime ?? null,
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
/**
|
|
1604
|
+
* Sets the automatic replies (OOF) settings.
|
|
1605
|
+
*/
|
|
1606
|
+
async setAutomaticRepliesAsync(params) {
|
|
1607
|
+
const settings = { status: params.status };
|
|
1608
|
+
if (params.externalAudience != null)
|
|
1609
|
+
settings['externalAudience'] = params.externalAudience;
|
|
1610
|
+
if (params.internalReplyMessage != null)
|
|
1611
|
+
settings['internalReplyMessage'] = params.internalReplyMessage;
|
|
1612
|
+
if (params.externalReplyMessage != null)
|
|
1613
|
+
settings['externalReplyMessage'] = params.externalReplyMessage;
|
|
1614
|
+
if (params.scheduledStartDateTime != null)
|
|
1615
|
+
settings['scheduledStartDateTime'] = { dateTime: params.scheduledStartDateTime, timeZone: 'UTC' };
|
|
1616
|
+
if (params.scheduledEndDateTime != null)
|
|
1617
|
+
settings['scheduledEndDateTime'] = { dateTime: params.scheduledEndDateTime, timeZone: 'UTC' };
|
|
1618
|
+
await this.client.setAutomaticReplies(settings);
|
|
1619
|
+
}
|
|
1620
|
+
// ===========================================================================
|
|
1621
|
+
// Mailbox Settings
|
|
1622
|
+
// ===========================================================================
|
|
1623
|
+
/**
|
|
1624
|
+
* Gets the current mailbox settings (language, time zone, formats, working hours).
|
|
1625
|
+
*/
|
|
1626
|
+
async getMailboxSettingsAsync() {
|
|
1627
|
+
const settings = await this.client.getMailboxSettings();
|
|
1628
|
+
const lang = settings.language;
|
|
1629
|
+
return {
|
|
1630
|
+
language: lang?.locale ?? null,
|
|
1631
|
+
timeZone: settings.timeZone ?? null,
|
|
1632
|
+
dateFormat: settings.dateFormat ?? null,
|
|
1633
|
+
timeFormat: settings.timeFormat ?? null,
|
|
1634
|
+
workingHours: settings.workingHours ?? null,
|
|
1635
|
+
};
|
|
1636
|
+
}
|
|
1637
|
+
/**
|
|
1638
|
+
* Updates mailbox settings (language, time zone, date/time formats).
|
|
1639
|
+
*/
|
|
1640
|
+
async updateMailboxSettingsAsync(params) {
|
|
1641
|
+
const settings = {};
|
|
1642
|
+
if (params.language != null)
|
|
1643
|
+
settings['language'] = { locale: params.language };
|
|
1644
|
+
if (params.timeZone != null)
|
|
1645
|
+
settings['timeZone'] = params.timeZone;
|
|
1646
|
+
if (params.dateFormat != null)
|
|
1647
|
+
settings['dateFormat'] = params.dateFormat;
|
|
1648
|
+
if (params.timeFormat != null)
|
|
1649
|
+
settings['timeFormat'] = params.timeFormat;
|
|
1650
|
+
await this.client.updateMailboxSettings(settings);
|
|
1651
|
+
}
|
|
1652
|
+
// ===========================================================================
|
|
1653
|
+
// Master Categories
|
|
1654
|
+
// ===========================================================================
|
|
1655
|
+
/**
|
|
1656
|
+
* Lists all master categories.
|
|
1657
|
+
*/
|
|
1658
|
+
async listCategoriesAsync() {
|
|
1659
|
+
const categories = await this.client.listMasterCategories();
|
|
1660
|
+
return categories.map((cat) => {
|
|
1661
|
+
const graphId = cat.id;
|
|
1662
|
+
const numericId = hashStringToNumber(graphId);
|
|
1663
|
+
this.idCache.categories.set(numericId, graphId);
|
|
1664
|
+
return {
|
|
1665
|
+
id: numericId,
|
|
1666
|
+
name: cat.displayName ?? '',
|
|
1667
|
+
color: cat.color ?? 'none',
|
|
1668
|
+
};
|
|
1669
|
+
});
|
|
1670
|
+
}
|
|
1671
|
+
/**
|
|
1672
|
+
* Creates a new master category.
|
|
1673
|
+
*/
|
|
1674
|
+
async createCategoryAsync(name, color) {
|
|
1675
|
+
const created = await this.client.createMasterCategory(name, color);
|
|
1676
|
+
const graphId = created.id;
|
|
1677
|
+
const numericId = hashStringToNumber(graphId);
|
|
1678
|
+
this.idCache.categories.set(numericId, graphId);
|
|
1679
|
+
return numericId;
|
|
1680
|
+
}
|
|
1681
|
+
/**
|
|
1682
|
+
* Deletes a master category.
|
|
1683
|
+
*/
|
|
1684
|
+
async deleteCategoryAsync(categoryId) {
|
|
1685
|
+
const graphId = this.idCache.categories.get(categoryId);
|
|
1686
|
+
if (graphId == null)
|
|
1687
|
+
throw new Error(`Category ID ${categoryId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1688
|
+
await this.client.deleteMasterCategory(graphId);
|
|
1689
|
+
this.idCache.categories.delete(categoryId);
|
|
1690
|
+
}
|
|
1691
|
+
// ===========================================================================
|
|
1692
|
+
// Focused Inbox Overrides
|
|
1693
|
+
// ===========================================================================
|
|
1694
|
+
/**
|
|
1695
|
+
* Lists all focused inbox overrides.
|
|
1696
|
+
*/
|
|
1697
|
+
async listFocusedOverridesAsync() {
|
|
1698
|
+
const overrides = await this.client.listFocusedOverrides();
|
|
1699
|
+
return overrides.map((o) => {
|
|
1700
|
+
const graphId = o.id;
|
|
1701
|
+
const numericId = hashStringToNumber(graphId);
|
|
1702
|
+
this.idCache.focusedOverrides.set(numericId, graphId);
|
|
1703
|
+
return {
|
|
1704
|
+
id: numericId,
|
|
1705
|
+
senderAddress: o.senderEmailAddress?.address ?? '',
|
|
1706
|
+
classifyAs: o.classifyAs ?? '',
|
|
1707
|
+
};
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
/**
|
|
1711
|
+
* Creates a focused inbox override.
|
|
1712
|
+
*/
|
|
1713
|
+
async createFocusedOverrideAsync(senderAddress, classifyAs) {
|
|
1714
|
+
const created = await this.client.createFocusedOverride(senderAddress, classifyAs);
|
|
1715
|
+
const graphId = created.id;
|
|
1716
|
+
const numericId = hashStringToNumber(graphId);
|
|
1717
|
+
this.idCache.focusedOverrides.set(numericId, graphId);
|
|
1718
|
+
return numericId;
|
|
1719
|
+
}
|
|
1720
|
+
/**
|
|
1721
|
+
* Deletes a focused inbox override.
|
|
1722
|
+
*/
|
|
1723
|
+
async deleteFocusedOverrideAsync(overrideId) {
|
|
1724
|
+
const graphId = this.idCache.focusedOverrides.get(overrideId);
|
|
1725
|
+
if (graphId == null)
|
|
1726
|
+
throw new Error(`Focused override ID ${overrideId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1727
|
+
await this.client.deleteFocusedOverride(graphId);
|
|
1728
|
+
this.idCache.focusedOverrides.delete(overrideId);
|
|
1729
|
+
}
|
|
1730
|
+
// ===========================================================================
|
|
1731
|
+
// Message Headers & MIME
|
|
1732
|
+
// ===========================================================================
|
|
1733
|
+
/**
|
|
1734
|
+
* Gets internet message headers for an email.
|
|
1735
|
+
*/
|
|
1736
|
+
async getMessageHeadersAsync(emailId) {
|
|
1737
|
+
const graphId = this.idCache.messages.get(emailId);
|
|
1738
|
+
if (graphId == null)
|
|
1739
|
+
throw new Error(`Email ID ${emailId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1740
|
+
return await this.client.getMessageHeaders(graphId);
|
|
1741
|
+
}
|
|
1742
|
+
/**
|
|
1743
|
+
* Gets the MIME content of a message and saves it as an .eml file.
|
|
1744
|
+
*/
|
|
1745
|
+
async getMessageMimeAsync(emailId) {
|
|
1746
|
+
const graphId = this.idCache.messages.get(emailId);
|
|
1747
|
+
if (graphId == null)
|
|
1748
|
+
throw new Error(`Email ID ${emailId} not found in cache. Try searching for or listing the item first to refresh the cache.`);
|
|
1749
|
+
const mime = await this.client.getMessageMime(graphId);
|
|
1750
|
+
const downloadDir = getDownloadDir();
|
|
1751
|
+
const filePath = path.join(downloadDir, `email-${emailId}.eml`);
|
|
1752
|
+
fs.writeFileSync(filePath, mime, 'utf-8');
|
|
1753
|
+
return { filePath };
|
|
1754
|
+
}
|
|
1755
|
+
// ===========================================================================
|
|
1756
|
+
// Mail Tips
|
|
1757
|
+
// ===========================================================================
|
|
1758
|
+
/**
|
|
1759
|
+
* Gets mail tips for the specified email addresses.
|
|
1760
|
+
*/
|
|
1761
|
+
async getMailTipsAsync(emailAddresses) {
|
|
1762
|
+
const tips = await this.client.getMailTips(emailAddresses);
|
|
1763
|
+
return tips.map((tip) => {
|
|
1764
|
+
const t = tip;
|
|
1765
|
+
const emailAddr = t.emailAddress;
|
|
1766
|
+
const autoReplies = t.automaticReplies;
|
|
1767
|
+
return {
|
|
1768
|
+
emailAddress: emailAddr?.address ?? '',
|
|
1769
|
+
automaticReplies: (autoReplies?.message != null && autoReplies.message !== '') ? { message: autoReplies.message } : null,
|
|
1770
|
+
mailboxFull: t.mailboxFull ?? false,
|
|
1771
|
+
deliveryRestricted: t.deliveryRestricted ?? false,
|
|
1772
|
+
externalMemberCount: t.externalMemberCount ?? 0,
|
|
1773
|
+
maxMessageSize: t.maxMessageSize ?? 0,
|
|
1774
|
+
};
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
// ===========================================================================
|
|
1778
|
+
// Calendar Groups
|
|
1779
|
+
// ===========================================================================
|
|
1780
|
+
/**
|
|
1781
|
+
* Lists all calendar groups.
|
|
1782
|
+
*/
|
|
1783
|
+
async listCalendarGroupsAsync() {
|
|
1784
|
+
const groups = await this.client.listCalendarGroups();
|
|
1785
|
+
return groups.map((group) => {
|
|
1786
|
+
const graphId = group.id;
|
|
1787
|
+
const numericId = hashStringToNumber(graphId);
|
|
1788
|
+
this.idCache.calendarGroups.set(numericId, graphId);
|
|
1789
|
+
return {
|
|
1790
|
+
id: numericId,
|
|
1791
|
+
name: group.name ?? '',
|
|
1792
|
+
classId: group.classId?.toString() ?? '',
|
|
1793
|
+
};
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
/**
|
|
1797
|
+
* Creates a new calendar group.
|
|
1798
|
+
*/
|
|
1799
|
+
async createCalendarGroupAsync(name) {
|
|
1800
|
+
const created = await this.client.createCalendarGroup(name);
|
|
1801
|
+
const graphId = created.id;
|
|
1802
|
+
const numericId = hashStringToNumber(graphId);
|
|
1803
|
+
this.idCache.calendarGroups.set(numericId, graphId);
|
|
1804
|
+
return numericId;
|
|
1805
|
+
}
|
|
1806
|
+
// ===========================================================================
|
|
1807
|
+
// Calendar Permissions
|
|
1808
|
+
// ===========================================================================
|
|
1809
|
+
/**
|
|
1810
|
+
* Lists all permissions for a calendar.
|
|
1811
|
+
*/
|
|
1812
|
+
async listCalendarPermissionsAsync(calendarId) {
|
|
1813
|
+
const graphCalendarId = this.idCache.calendars.get(calendarId);
|
|
1814
|
+
if (graphCalendarId == null) {
|
|
1815
|
+
throw new Error(`Calendar ID ${calendarId} not found in cache. Please call list_calendars first.`);
|
|
1816
|
+
}
|
|
1817
|
+
const permissions = await this.client.listCalendarPermissions(graphCalendarId);
|
|
1818
|
+
return permissions.map((perm) => {
|
|
1819
|
+
const graphPermId = perm.id;
|
|
1820
|
+
const numericId = hashStringToNumber(graphPermId);
|
|
1821
|
+
this.idCache.calendarPermissions.set(numericId, { calendarId: graphCalendarId, permissionId: graphPermId });
|
|
1822
|
+
return {
|
|
1823
|
+
id: numericId,
|
|
1824
|
+
emailAddress: perm.emailAddress?.address ?? '',
|
|
1825
|
+
role: perm.role ?? 'none',
|
|
1826
|
+
isRemovable: perm.isRemovable ?? false,
|
|
1827
|
+
isInsideOrganization: perm.isInsideOrganization ?? false,
|
|
1828
|
+
};
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
/**
|
|
1832
|
+
* Creates a calendar permission (shares a calendar with someone).
|
|
1833
|
+
*/
|
|
1834
|
+
async createCalendarPermissionAsync(calendarId, email, role) {
|
|
1835
|
+
const graphCalendarId = this.idCache.calendars.get(calendarId);
|
|
1836
|
+
if (graphCalendarId == null) {
|
|
1837
|
+
throw new Error(`Calendar ID ${calendarId} not found in cache. Please call list_calendars first.`);
|
|
1838
|
+
}
|
|
1839
|
+
const permission = await this.client.createCalendarPermission(graphCalendarId, {
|
|
1840
|
+
emailAddress: {
|
|
1841
|
+
address: email,
|
|
1842
|
+
name: email,
|
|
1843
|
+
},
|
|
1844
|
+
role,
|
|
1845
|
+
});
|
|
1846
|
+
const graphPermId = permission.id;
|
|
1847
|
+
const numericId = hashStringToNumber(graphPermId);
|
|
1848
|
+
this.idCache.calendarPermissions.set(numericId, { calendarId: graphCalendarId, permissionId: graphPermId });
|
|
1849
|
+
return numericId;
|
|
1850
|
+
}
|
|
1851
|
+
/**
|
|
1852
|
+
* Deletes a calendar permission.
|
|
1853
|
+
*/
|
|
1854
|
+
async deleteCalendarPermissionAsync(permissionId) {
|
|
1855
|
+
const cached = this.idCache.calendarPermissions.get(permissionId);
|
|
1856
|
+
if (cached == null) {
|
|
1857
|
+
throw new Error(`Calendar permission ID ${permissionId} not found in cache. Please call list_calendar_permissions first.`);
|
|
1858
|
+
}
|
|
1859
|
+
await this.client.deleteCalendarPermission(cached.calendarId, cached.permissionId);
|
|
1860
|
+
this.idCache.calendarPermissions.delete(permissionId);
|
|
1861
|
+
}
|
|
1862
|
+
// ===========================================================================
|
|
1863
|
+
// Room Lists & Rooms
|
|
1864
|
+
// ===========================================================================
|
|
1865
|
+
/**
|
|
1866
|
+
* Lists all room lists.
|
|
1867
|
+
*/
|
|
1868
|
+
async listRoomListsAsync() {
|
|
1869
|
+
const lists = await this.client.listRoomLists();
|
|
1870
|
+
return lists.map((item) => ({
|
|
1871
|
+
name: item.name ?? '',
|
|
1872
|
+
address: item.address ?? '',
|
|
1873
|
+
}));
|
|
1874
|
+
}
|
|
1875
|
+
/**
|
|
1876
|
+
* Lists rooms, optionally filtered by a room list email.
|
|
1877
|
+
*/
|
|
1878
|
+
async listRoomsAsync(roomListEmail) {
|
|
1879
|
+
const rooms = await this.client.listRooms(roomListEmail);
|
|
1880
|
+
return rooms.map((item) => ({
|
|
1881
|
+
name: item.name ?? '',
|
|
1882
|
+
address: item.address ?? '',
|
|
1883
|
+
}));
|
|
1884
|
+
}
|
|
1885
|
+
// ===========================================================================
|
|
1886
|
+
// Teams
|
|
1887
|
+
// ===========================================================================
|
|
1888
|
+
/**
|
|
1889
|
+
* Lists all joined teams with cached numeric IDs.
|
|
1890
|
+
*/
|
|
1891
|
+
async listTeamsAsync() {
|
|
1892
|
+
const teams = await this.client.listJoinedTeams();
|
|
1893
|
+
return teams.map((team) => {
|
|
1894
|
+
const graphId = team.id;
|
|
1895
|
+
const numericId = hashStringToNumber(graphId);
|
|
1896
|
+
this.idCache.teams.set(numericId, graphId);
|
|
1897
|
+
return { id: numericId, name: team.displayName ?? '', description: team.description ?? '' };
|
|
1898
|
+
});
|
|
1899
|
+
}
|
|
1900
|
+
/**
|
|
1901
|
+
* Lists all channels in a team with cached numeric IDs.
|
|
1902
|
+
*/
|
|
1903
|
+
async listChannelsAsync(teamId) {
|
|
1904
|
+
const graphTeamId = this.idCache.teams.get(teamId);
|
|
1905
|
+
if (graphTeamId == null)
|
|
1906
|
+
throw new Error(`Team ID ${teamId} not found in cache. Try listing teams first.`);
|
|
1907
|
+
const channels = await this.client.listChannels(graphTeamId);
|
|
1908
|
+
return channels.map((ch) => {
|
|
1909
|
+
const graphId = ch.id;
|
|
1910
|
+
const numericId = hashStringToNumber(graphId);
|
|
1911
|
+
this.idCache.channels.set(numericId, { teamId: graphTeamId, channelId: graphId });
|
|
1912
|
+
return { id: numericId, name: ch.displayName ?? '', description: ch.description ?? '', membershipType: ch.membershipType ?? 'standard' };
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1915
|
+
/**
|
|
1916
|
+
* Gets a specific channel by cached numeric ID.
|
|
1917
|
+
*/
|
|
1918
|
+
async getChannelAsync(channelId) {
|
|
1919
|
+
const cached = this.idCache.channels.get(channelId);
|
|
1920
|
+
if (cached == null)
|
|
1921
|
+
throw new Error(`Channel ID ${channelId} not found in cache. Try listing channels first.`);
|
|
1922
|
+
const ch = await this.client.getChannel(cached.teamId, cached.channelId);
|
|
1923
|
+
return { id: channelId, name: ch.displayName ?? '', description: ch.description ?? '', membershipType: ch.membershipType ?? 'standard', webUrl: ch.webUrl ?? '' };
|
|
1924
|
+
}
|
|
1925
|
+
/**
|
|
1926
|
+
* Creates a new channel in a team.
|
|
1927
|
+
*/
|
|
1928
|
+
async createChannelAsync(teamId, name, description) {
|
|
1929
|
+
const graphTeamId = this.idCache.teams.get(teamId);
|
|
1930
|
+
if (graphTeamId == null)
|
|
1931
|
+
throw new Error(`Team ID ${teamId} not found in cache. Try listing teams first.`);
|
|
1932
|
+
const ch = await this.client.createChannel(graphTeamId, name, description);
|
|
1933
|
+
const graphId = ch.id;
|
|
1934
|
+
const numericId = hashStringToNumber(graphId);
|
|
1935
|
+
this.idCache.channels.set(numericId, { teamId: graphTeamId, channelId: graphId });
|
|
1936
|
+
return numericId;
|
|
1937
|
+
}
|
|
1938
|
+
/**
|
|
1939
|
+
* Updates a channel's properties.
|
|
1940
|
+
*/
|
|
1941
|
+
async updateChannelAsync(channelId, updates) {
|
|
1942
|
+
const cached = this.idCache.channels.get(channelId);
|
|
1943
|
+
if (cached == null)
|
|
1944
|
+
throw new Error(`Channel ID ${channelId} not found in cache. Try listing channels first.`);
|
|
1945
|
+
const graphUpdates = {};
|
|
1946
|
+
if (updates.name != null)
|
|
1947
|
+
graphUpdates['displayName'] = updates.name;
|
|
1948
|
+
if (updates.description != null)
|
|
1949
|
+
graphUpdates['description'] = updates.description;
|
|
1950
|
+
await this.client.updateChannel(cached.teamId, cached.channelId, graphUpdates);
|
|
1951
|
+
}
|
|
1952
|
+
/**
|
|
1953
|
+
* Deletes a channel.
|
|
1954
|
+
*/
|
|
1955
|
+
async deleteChannelAsync(channelId) {
|
|
1956
|
+
const cached = this.idCache.channels.get(channelId);
|
|
1957
|
+
if (cached == null)
|
|
1958
|
+
throw new Error(`Channel ID ${channelId} not found in cache. Try listing channels first.`);
|
|
1959
|
+
await this.client.deleteChannel(cached.teamId, cached.channelId);
|
|
1960
|
+
this.idCache.channels.delete(channelId);
|
|
1961
|
+
}
|
|
1962
|
+
/**
|
|
1963
|
+
* Lists members of a team.
|
|
1964
|
+
*/
|
|
1965
|
+
async listTeamMembersAsync(teamId) {
|
|
1966
|
+
const graphTeamId = this.idCache.teams.get(teamId);
|
|
1967
|
+
if (graphTeamId == null)
|
|
1968
|
+
throw new Error(`Team ID ${teamId} not found in cache. Try listing teams first.`);
|
|
1969
|
+
const members = await this.client.listTeamMembers(graphTeamId);
|
|
1970
|
+
return members.map((m) => ({
|
|
1971
|
+
id: m.id ?? '',
|
|
1972
|
+
displayName: m.displayName ?? '',
|
|
1973
|
+
email: m.email ?? '',
|
|
1974
|
+
roles: m.roles ?? [],
|
|
1975
|
+
}));
|
|
1976
|
+
}
|
|
1977
|
+
// ===========================================================================
|
|
1978
|
+
// Channel Messages
|
|
1979
|
+
// ===========================================================================
|
|
1980
|
+
/**
|
|
1981
|
+
* Lists recent messages in a channel.
|
|
1982
|
+
*/
|
|
1983
|
+
async listChannelMessagesAsync(channelId, limit = 25) {
|
|
1984
|
+
const cached = this.idCache.channels.get(channelId);
|
|
1985
|
+
if (cached == null)
|
|
1986
|
+
throw new Error(`Channel ID ${channelId} not found in cache. Try listing channels first.`);
|
|
1987
|
+
const messages = await this.client.listChannelMessages(cached.teamId, cached.channelId, limit);
|
|
1988
|
+
return messages.map((msg) => {
|
|
1989
|
+
const graphId = msg.id;
|
|
1990
|
+
const numericId = hashStringToNumber(graphId);
|
|
1991
|
+
this.idCache.channelMessages.set(numericId, { teamId: cached.teamId, channelId: cached.channelId, messageId: graphId });
|
|
1992
|
+
return {
|
|
1993
|
+
id: numericId,
|
|
1994
|
+
senderName: msg.from?.user?.displayName ?? msg.from?.application?.displayName ?? '',
|
|
1995
|
+
senderEmail: msg.from?.user?.email ?? '',
|
|
1996
|
+
bodyPreview: msg.body?.content?.substring(0, 200) ?? '',
|
|
1997
|
+
bodyContent: msg.body?.content ?? '',
|
|
1998
|
+
contentType: msg.body?.contentType ?? 'html',
|
|
1999
|
+
createdDateTime: msg.createdDateTime ?? '',
|
|
2000
|
+
};
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
/**
|
|
2004
|
+
* Gets a specific channel message with its replies.
|
|
2005
|
+
*/
|
|
2006
|
+
async getChannelMessageAsync(messageId) {
|
|
2007
|
+
const cached = this.idCache.channelMessages.get(messageId);
|
|
2008
|
+
if (cached == null)
|
|
2009
|
+
throw new Error(`Message ID ${messageId} not found in cache. Try listing channel messages first.`);
|
|
2010
|
+
const [msg, repliesRaw] = await Promise.all([
|
|
2011
|
+
this.client.getChannelMessage(cached.teamId, cached.channelId, cached.messageId),
|
|
2012
|
+
this.client.listChannelMessageReplies(cached.teamId, cached.channelId, cached.messageId),
|
|
2013
|
+
]);
|
|
2014
|
+
const replies = repliesRaw.map((r) => {
|
|
2015
|
+
const rGraphId = r.id;
|
|
2016
|
+
const rNumericId = hashStringToNumber(rGraphId);
|
|
2017
|
+
return {
|
|
2018
|
+
id: rNumericId,
|
|
2019
|
+
senderName: r.from?.user?.displayName ?? r.from?.application?.displayName ?? '',
|
|
2020
|
+
senderEmail: r.from?.user?.email ?? '',
|
|
2021
|
+
bodyContent: r.body?.content ?? '',
|
|
2022
|
+
contentType: r.body?.contentType ?? 'html',
|
|
2023
|
+
createdDateTime: r.createdDateTime ?? '',
|
|
2024
|
+
};
|
|
2025
|
+
});
|
|
2026
|
+
return {
|
|
2027
|
+
id: messageId,
|
|
2028
|
+
senderName: msg.from?.user?.displayName ?? msg.from?.application?.displayName ?? '',
|
|
2029
|
+
senderEmail: msg.from?.user?.email ?? '',
|
|
2030
|
+
bodyContent: msg.body?.content ?? '',
|
|
2031
|
+
contentType: msg.body?.contentType ?? 'html',
|
|
2032
|
+
createdDateTime: msg.createdDateTime ?? '',
|
|
2033
|
+
replies,
|
|
2034
|
+
};
|
|
2035
|
+
}
|
|
2036
|
+
/**
|
|
2037
|
+
* Sends a new message to a channel.
|
|
2038
|
+
*/
|
|
2039
|
+
async sendChannelMessageAsync(channelId, body, contentType = 'html') {
|
|
2040
|
+
const cached = this.idCache.channels.get(channelId);
|
|
2041
|
+
if (cached == null)
|
|
2042
|
+
throw new Error(`Channel ID ${channelId} not found in cache. Try listing channels first.`);
|
|
2043
|
+
const msg = await this.client.sendChannelMessage(cached.teamId, cached.channelId, body, contentType);
|
|
2044
|
+
const graphId = msg.id;
|
|
2045
|
+
const numericId = hashStringToNumber(graphId);
|
|
2046
|
+
this.idCache.channelMessages.set(numericId, { teamId: cached.teamId, channelId: cached.channelId, messageId: graphId });
|
|
2047
|
+
return numericId;
|
|
2048
|
+
}
|
|
2049
|
+
/**
|
|
2050
|
+
* Replies to a channel message.
|
|
2051
|
+
*/
|
|
2052
|
+
async replyToChannelMessageAsync(messageId, body, contentType = 'html') {
|
|
2053
|
+
const cached = this.idCache.channelMessages.get(messageId);
|
|
2054
|
+
if (cached == null)
|
|
2055
|
+
throw new Error(`Message ID ${messageId} not found in cache. Try listing channel messages first.`);
|
|
2056
|
+
const reply = await this.client.replyToChannelMessage(cached.teamId, cached.channelId, cached.messageId, body, contentType);
|
|
2057
|
+
const graphId = reply.id;
|
|
2058
|
+
const numericId = hashStringToNumber(graphId);
|
|
2059
|
+
this.idCache.channelMessages.set(numericId, { teamId: cached.teamId, channelId: cached.channelId, messageId: graphId });
|
|
2060
|
+
return numericId;
|
|
2061
|
+
}
|
|
2062
|
+
// ===========================================================================
|
|
2063
|
+
// Chats
|
|
2064
|
+
// ===========================================================================
|
|
2065
|
+
async listChatsAsync(limit = 25) {
|
|
2066
|
+
const chats = await this.client.listChats(limit);
|
|
2067
|
+
return chats.map((chat) => {
|
|
2068
|
+
const graphId = chat.id;
|
|
2069
|
+
const numericId = hashStringToNumber(graphId);
|
|
2070
|
+
this.idCache.chats.set(numericId, graphId);
|
|
2071
|
+
const chatRecord = chat;
|
|
2072
|
+
const preview = chatRecord.lastMessagePreview;
|
|
2073
|
+
const previewBody = preview?.body;
|
|
2074
|
+
const previewContent = previewBody?.content?.substring(0, 200) ?? '';
|
|
2075
|
+
return {
|
|
2076
|
+
id: numericId,
|
|
2077
|
+
topic: chat.topic ?? '',
|
|
2078
|
+
chatType: chat.chatType ?? 'oneOnOne',
|
|
2079
|
+
lastMessagePreview: previewContent,
|
|
2080
|
+
createdDateTime: chat.createdDateTime ?? '',
|
|
2081
|
+
};
|
|
2082
|
+
});
|
|
2083
|
+
}
|
|
2084
|
+
async getChatAsync(chatId) {
|
|
2085
|
+
const graphId = this.idCache.chats.get(chatId);
|
|
2086
|
+
if (graphId == null)
|
|
2087
|
+
throw new Error(`Chat ID ${chatId} not found in cache. Try listing chats first.`);
|
|
2088
|
+
const chat = await this.client.getChat(graphId);
|
|
2089
|
+
return {
|
|
2090
|
+
id: chatId,
|
|
2091
|
+
topic: chat.topic ?? '',
|
|
2092
|
+
chatType: chat.chatType ?? 'oneOnOne',
|
|
2093
|
+
createdDateTime: chat.createdDateTime ?? '',
|
|
2094
|
+
webUrl: chat.webUrl ?? '',
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
async listChatMessagesAsync(chatId, limit = 25) {
|
|
2098
|
+
const graphChatId = this.idCache.chats.get(chatId);
|
|
2099
|
+
if (graphChatId == null)
|
|
2100
|
+
throw new Error(`Chat ID ${chatId} not found in cache. Try listing chats first.`);
|
|
2101
|
+
const messages = await this.client.listChatMessages(graphChatId, limit);
|
|
2102
|
+
return messages.map((msg) => {
|
|
2103
|
+
const graphId = msg.id;
|
|
2104
|
+
const numericId = hashStringToNumber(graphId);
|
|
2105
|
+
this.idCache.chatMessages.set(numericId, { chatId: graphChatId, messageId: graphId });
|
|
2106
|
+
return {
|
|
2107
|
+
id: numericId,
|
|
2108
|
+
senderName: msg.from?.user?.displayName ?? msg.from?.application?.displayName ?? '',
|
|
2109
|
+
senderEmail: msg.from?.user?.email ?? '',
|
|
2110
|
+
bodyPreview: msg.body?.content?.substring(0, 200) ?? '',
|
|
2111
|
+
bodyContent: msg.body?.content ?? '',
|
|
2112
|
+
contentType: msg.body?.contentType ?? 'html',
|
|
2113
|
+
createdDateTime: msg.createdDateTime ?? '',
|
|
2114
|
+
};
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2117
|
+
async sendChatMessageAsync(chatId, body, contentType = 'html') {
|
|
2118
|
+
const graphChatId = this.idCache.chats.get(chatId);
|
|
2119
|
+
if (graphChatId == null)
|
|
2120
|
+
throw new Error(`Chat ID ${chatId} not found in cache. Try listing chats first.`);
|
|
2121
|
+
const msg = await this.client.sendChatMessage(graphChatId, body, contentType);
|
|
2122
|
+
const graphId = msg.id;
|
|
2123
|
+
const numericId = hashStringToNumber(graphId);
|
|
2124
|
+
this.idCache.chatMessages.set(numericId, { chatId: graphChatId, messageId: graphId });
|
|
2125
|
+
return numericId;
|
|
2126
|
+
}
|
|
2127
|
+
// ===========================================================================
|
|
2128
|
+
// Message Reactions
|
|
2129
|
+
// ===========================================================================
|
|
2130
|
+
async listMessageReactionsAsync(messageId, messageType) {
|
|
2131
|
+
const mapReactions = (msg) => {
|
|
2132
|
+
const reactions = (msg.reactions ?? []);
|
|
2133
|
+
return reactions.map((r) => {
|
|
2134
|
+
const userObj = r.user;
|
|
2135
|
+
const innerUser = userObj?.user;
|
|
2136
|
+
return {
|
|
2137
|
+
reactionType: r.reactionType ?? '',
|
|
2138
|
+
user: { displayName: innerUser?.displayName ?? '' },
|
|
2139
|
+
createdDateTime: r.createdDateTime ?? '',
|
|
2140
|
+
};
|
|
2141
|
+
});
|
|
2142
|
+
};
|
|
2143
|
+
if (messageType === 'channel') {
|
|
2144
|
+
const cached = this.idCache.channelMessages.get(messageId);
|
|
2145
|
+
if (cached == null)
|
|
2146
|
+
throw new Error(`Message ID ${messageId} not found in cache. Try listing channel messages first.`);
|
|
2147
|
+
const msg = await this.client.getChannelMessage(cached.teamId, cached.channelId, cached.messageId);
|
|
2148
|
+
return mapReactions(msg);
|
|
2149
|
+
}
|
|
2150
|
+
else {
|
|
2151
|
+
const cached = this.idCache.chatMessages.get(messageId);
|
|
2152
|
+
if (cached == null)
|
|
2153
|
+
throw new Error(`Message ID ${messageId} not found in cache. Try listing chat messages first.`);
|
|
2154
|
+
const msg = await this.client.getChatMessage(cached.chatId, cached.messageId);
|
|
2155
|
+
return mapReactions(msg);
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
async addMessageReactionAsync(messageId, messageType, reactionType) {
|
|
2159
|
+
if (messageType === 'channel') {
|
|
2160
|
+
const cached = this.idCache.channelMessages.get(messageId);
|
|
2161
|
+
if (cached == null)
|
|
2162
|
+
throw new Error(`Message ID ${messageId} not found in cache. Try listing channel messages first.`);
|
|
2163
|
+
await this.client.setChannelMessageReaction(cached.teamId, cached.channelId, cached.messageId, reactionType);
|
|
2164
|
+
}
|
|
2165
|
+
else {
|
|
2166
|
+
const cached = this.idCache.chatMessages.get(messageId);
|
|
2167
|
+
if (cached == null)
|
|
2168
|
+
throw new Error(`Message ID ${messageId} not found in cache. Try listing chat messages first.`);
|
|
2169
|
+
await this.client.setChatMessageReaction(cached.chatId, cached.messageId, reactionType);
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
async removeMessageReactionAsync(messageId, messageType, reactionType) {
|
|
2173
|
+
if (messageType === 'channel') {
|
|
2174
|
+
const cached = this.idCache.channelMessages.get(messageId);
|
|
2175
|
+
if (cached == null)
|
|
2176
|
+
throw new Error(`Message ID ${messageId} not found in cache. Try listing channel messages first.`);
|
|
2177
|
+
await this.client.unsetChannelMessageReaction(cached.teamId, cached.channelId, cached.messageId, reactionType);
|
|
2178
|
+
}
|
|
2179
|
+
else {
|
|
2180
|
+
const cached = this.idCache.chatMessages.get(messageId);
|
|
2181
|
+
if (cached == null)
|
|
2182
|
+
throw new Error(`Message ID ${messageId} not found in cache. Try listing chat messages first.`);
|
|
2183
|
+
await this.client.unsetChatMessageReaction(cached.chatId, cached.messageId, reactionType);
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
async listChatMembersAsync(chatId) {
|
|
2187
|
+
const graphChatId = this.idCache.chats.get(chatId);
|
|
2188
|
+
if (graphChatId == null)
|
|
2189
|
+
throw new Error(`Chat ID ${chatId} not found in cache. Try listing chats first.`);
|
|
2190
|
+
const members = await this.client.listChatMembers(graphChatId);
|
|
2191
|
+
return members.map((m) => ({
|
|
2192
|
+
displayName: m.displayName ?? '',
|
|
2193
|
+
email: m.email ?? '',
|
|
2194
|
+
roles: m.roles ?? [],
|
|
2195
|
+
}));
|
|
2196
|
+
}
|
|
2197
|
+
// ===========================================================================
|
|
2198
|
+
// Planner Plans
|
|
2199
|
+
// ===========================================================================
|
|
2200
|
+
/**
|
|
2201
|
+
* Lists all plans the current user has.
|
|
2202
|
+
*/
|
|
2203
|
+
async listPlansAsync() {
|
|
2204
|
+
const plans = await this.client.listPlans();
|
|
2205
|
+
return plans.map((plan) => {
|
|
2206
|
+
const graphId = plan.id;
|
|
2207
|
+
const numericId = hashStringToNumber(graphId);
|
|
2208
|
+
const etag = plan['@odata.etag'] ?? '';
|
|
2209
|
+
this.idCache.plans.set(numericId, { planId: graphId, etag });
|
|
2210
|
+
return {
|
|
2211
|
+
id: numericId,
|
|
2212
|
+
title: plan.title ?? '',
|
|
2213
|
+
owner: plan.owner ?? '',
|
|
2214
|
+
createdDateTime: plan.createdDateTime ?? '',
|
|
2215
|
+
};
|
|
2216
|
+
});
|
|
2217
|
+
}
|
|
2218
|
+
/**
|
|
2219
|
+
* Gets a specific plan by cached numeric ID.
|
|
2220
|
+
*/
|
|
2221
|
+
async getPlanAsync(planId) {
|
|
2222
|
+
const cached = this.idCache.plans.get(planId);
|
|
2223
|
+
if (cached == null)
|
|
2224
|
+
throw new Error(`Plan ID ${planId} not found in cache. Try listing plans first.`);
|
|
2225
|
+
const plan = await this.client.getPlan(cached.planId);
|
|
2226
|
+
const etag = plan['@odata.etag'] ?? '';
|
|
2227
|
+
this.idCache.plans.set(planId, { planId: cached.planId, etag });
|
|
2228
|
+
return {
|
|
2229
|
+
id: planId,
|
|
2230
|
+
title: plan.title ?? '',
|
|
2231
|
+
owner: plan.owner ?? '',
|
|
2232
|
+
createdDateTime: plan.createdDateTime ?? '',
|
|
2233
|
+
etag,
|
|
2234
|
+
};
|
|
2235
|
+
}
|
|
2236
|
+
/**
|
|
2237
|
+
* Creates a new plan.
|
|
2238
|
+
*/
|
|
2239
|
+
async createPlanAsync(title, groupId) {
|
|
2240
|
+
const plan = await this.client.createPlan(title, groupId);
|
|
2241
|
+
const graphId = plan.id;
|
|
2242
|
+
const numericId = hashStringToNumber(graphId);
|
|
2243
|
+
const etag = plan['@odata.etag'] ?? '';
|
|
2244
|
+
this.idCache.plans.set(numericId, { planId: graphId, etag });
|
|
2245
|
+
return numericId;
|
|
2246
|
+
}
|
|
2247
|
+
/**
|
|
2248
|
+
* Updates a plan (requires cached ETag).
|
|
2249
|
+
*/
|
|
2250
|
+
async updatePlanAsync(planId, updates) {
|
|
2251
|
+
const cached = this.idCache.plans.get(planId);
|
|
2252
|
+
if (cached == null)
|
|
2253
|
+
throw new Error(`Plan ID ${planId} not found in cache. Try listing plans first.`);
|
|
2254
|
+
const graphUpdates = {};
|
|
2255
|
+
if (updates.title != null)
|
|
2256
|
+
graphUpdates['title'] = updates.title;
|
|
2257
|
+
const result = await this.client.updatePlan(cached.planId, graphUpdates, cached.etag);
|
|
2258
|
+
const newEtag = result['@odata.etag'] ?? cached.etag;
|
|
2259
|
+
this.idCache.plans.set(planId, { planId: cached.planId, etag: newEtag });
|
|
2260
|
+
}
|
|
2261
|
+
// ===========================================================================
|
|
2262
|
+
// Planner Buckets
|
|
2263
|
+
// ===========================================================================
|
|
2264
|
+
/**
|
|
2265
|
+
* Lists all buckets in a plan.
|
|
2266
|
+
*/
|
|
2267
|
+
async listBucketsAsync(planId) {
|
|
2268
|
+
const cached = this.idCache.plans.get(planId);
|
|
2269
|
+
if (cached == null)
|
|
2270
|
+
throw new Error(`Plan ID ${planId} not found in cache. Try listing plans first.`);
|
|
2271
|
+
const buckets = await this.client.listBuckets(cached.planId);
|
|
2272
|
+
return buckets.map((bucket) => {
|
|
2273
|
+
const graphId = bucket.id;
|
|
2274
|
+
const numericId = hashStringToNumber(graphId);
|
|
2275
|
+
const etag = bucket['@odata.etag'] ?? '';
|
|
2276
|
+
this.idCache.plannerBuckets.set(numericId, { planId: cached.planId, bucketId: graphId, etag });
|
|
2277
|
+
return {
|
|
2278
|
+
id: numericId,
|
|
2279
|
+
name: bucket.name ?? '',
|
|
2280
|
+
planId,
|
|
2281
|
+
orderHint: bucket.orderHint ?? '',
|
|
2282
|
+
};
|
|
2283
|
+
});
|
|
2284
|
+
}
|
|
2285
|
+
/**
|
|
2286
|
+
* Creates a new bucket in a plan.
|
|
2287
|
+
*/
|
|
2288
|
+
async createBucketAsync(planId, name) {
|
|
2289
|
+
const cached = this.idCache.plans.get(planId);
|
|
2290
|
+
if (cached == null)
|
|
2291
|
+
throw new Error(`Plan ID ${planId} not found in cache. Try listing plans first.`);
|
|
2292
|
+
const bucket = await this.client.createBucket(cached.planId, name);
|
|
2293
|
+
const graphId = bucket.id;
|
|
2294
|
+
const numericId = hashStringToNumber(graphId);
|
|
2295
|
+
const etag = bucket['@odata.etag'] ?? '';
|
|
2296
|
+
this.idCache.plannerBuckets.set(numericId, { planId: cached.planId, bucketId: graphId, etag });
|
|
2297
|
+
return numericId;
|
|
2298
|
+
}
|
|
2299
|
+
/**
|
|
2300
|
+
* Updates a bucket (requires cached ETag).
|
|
2301
|
+
*/
|
|
2302
|
+
async updateBucketAsync(bucketId, updates) {
|
|
2303
|
+
const cached = this.idCache.plannerBuckets.get(bucketId);
|
|
2304
|
+
if (cached == null)
|
|
2305
|
+
throw new Error(`Bucket ID ${bucketId} not found in cache. Try listing buckets first.`);
|
|
2306
|
+
const graphUpdates = {};
|
|
2307
|
+
if (updates.name != null)
|
|
2308
|
+
graphUpdates['name'] = updates.name;
|
|
2309
|
+
const result = await this.client.updateBucket(cached.bucketId, graphUpdates, cached.etag);
|
|
2310
|
+
const newEtag = result['@odata.etag'] ?? cached.etag;
|
|
2311
|
+
this.idCache.plannerBuckets.set(bucketId, { planId: cached.planId, bucketId: cached.bucketId, etag: newEtag });
|
|
2312
|
+
}
|
|
2313
|
+
/**
|
|
2314
|
+
* Deletes a bucket (requires cached ETag).
|
|
2315
|
+
*/
|
|
2316
|
+
async deleteBucketAsync(bucketId) {
|
|
2317
|
+
const cached = this.idCache.plannerBuckets.get(bucketId);
|
|
2318
|
+
if (cached == null)
|
|
2319
|
+
throw new Error(`Bucket ID ${bucketId} not found in cache. Try listing buckets first.`);
|
|
2320
|
+
await this.client.deleteBucket(cached.bucketId, cached.etag);
|
|
2321
|
+
this.idCache.plannerBuckets.delete(bucketId);
|
|
2322
|
+
}
|
|
2323
|
+
// ===========================================================================
|
|
2324
|
+
// Planner Tasks
|
|
2325
|
+
// ===========================================================================
|
|
2326
|
+
/**
|
|
2327
|
+
* Lists all tasks in a plan.
|
|
2328
|
+
*/
|
|
2329
|
+
async listPlannerTasksAsync(planId) {
|
|
2330
|
+
const cached = this.idCache.plans.get(planId);
|
|
2331
|
+
if (cached == null)
|
|
2332
|
+
throw new Error(`Plan ID ${planId} not found in cache. Try listing plans first.`);
|
|
2333
|
+
const tasks = await this.client.listPlannerTasks(cached.planId);
|
|
2334
|
+
return tasks.map((task) => {
|
|
2335
|
+
const graphId = task.id;
|
|
2336
|
+
const numericId = hashStringToNumber(graphId);
|
|
2337
|
+
const etag = task['@odata.etag'] ?? '';
|
|
2338
|
+
this.idCache.plannerTasks.set(numericId, { taskId: graphId, etag });
|
|
2339
|
+
// Resolve bucket numeric ID if present
|
|
2340
|
+
let bucketNumericId = null;
|
|
2341
|
+
if (task.bucketId != null) {
|
|
2342
|
+
bucketNumericId = hashStringToNumber(task.bucketId);
|
|
2343
|
+
}
|
|
2344
|
+
return {
|
|
2345
|
+
id: numericId,
|
|
2346
|
+
title: task.title ?? '',
|
|
2347
|
+
bucketId: bucketNumericId,
|
|
2348
|
+
assignees: task.assignments != null ? Object.keys(task.assignments) : [],
|
|
2349
|
+
percentComplete: task.percentComplete ?? 0,
|
|
2350
|
+
priority: task.priority ?? 5,
|
|
2351
|
+
startDateTime: task.startDateTime ?? '',
|
|
2352
|
+
dueDateTime: task.dueDateTime ?? '',
|
|
2353
|
+
createdDateTime: task.createdDateTime ?? '',
|
|
2354
|
+
};
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
/**
|
|
2358
|
+
* Gets a specific planner task by cached numeric ID.
|
|
2359
|
+
*/
|
|
2360
|
+
async getPlannerTaskAsync(taskId) {
|
|
2361
|
+
const cached = this.idCache.plannerTasks.get(taskId);
|
|
2362
|
+
if (cached == null)
|
|
2363
|
+
throw new Error(`Task ID ${taskId} not found in cache. Try listing planner tasks first.`);
|
|
2364
|
+
const task = await this.client.getPlannerTask(cached.taskId);
|
|
2365
|
+
const etag = task['@odata.etag'] ?? '';
|
|
2366
|
+
this.idCache.plannerTasks.set(taskId, { taskId: cached.taskId, etag });
|
|
2367
|
+
let bucketNumericId = null;
|
|
2368
|
+
if (task.bucketId != null) {
|
|
2369
|
+
bucketNumericId = hashStringToNumber(task.bucketId);
|
|
2370
|
+
}
|
|
2371
|
+
return {
|
|
2372
|
+
id: taskId,
|
|
2373
|
+
title: task.title ?? '',
|
|
2374
|
+
bucketId: bucketNumericId,
|
|
2375
|
+
assignees: task.assignments != null ? Object.keys(task.assignments) : [],
|
|
2376
|
+
percentComplete: task.percentComplete ?? 0,
|
|
2377
|
+
priority: task.priority ?? 5,
|
|
2378
|
+
startDateTime: task.startDateTime ?? '',
|
|
2379
|
+
dueDateTime: task.dueDateTime ?? '',
|
|
2380
|
+
createdDateTime: task.createdDateTime ?? '',
|
|
2381
|
+
conversationThreadId: task.conversationThreadId ?? '',
|
|
2382
|
+
orderHint: task.orderHint ?? '',
|
|
2383
|
+
etag,
|
|
2384
|
+
};
|
|
2385
|
+
}
|
|
2386
|
+
/**
|
|
2387
|
+
* Creates a new planner task.
|
|
2388
|
+
*/
|
|
2389
|
+
async createPlannerTaskAsync(planId, title, bucketId, assignments, priority, startDate, dueDate) {
|
|
2390
|
+
const cachedPlan = this.idCache.plans.get(planId);
|
|
2391
|
+
if (cachedPlan == null)
|
|
2392
|
+
throw new Error(`Plan ID ${planId} not found in cache. Try listing plans first.`);
|
|
2393
|
+
const body = { planId: cachedPlan.planId, title };
|
|
2394
|
+
if (bucketId != null) {
|
|
2395
|
+
const cachedBucket = this.idCache.plannerBuckets.get(bucketId);
|
|
2396
|
+
if (cachedBucket == null)
|
|
2397
|
+
throw new Error(`Bucket ID ${bucketId} not found in cache. Try listing buckets first.`);
|
|
2398
|
+
body.bucketId = cachedBucket.bucketId;
|
|
2399
|
+
}
|
|
2400
|
+
if (assignments != null)
|
|
2401
|
+
body.assignments = assignments;
|
|
2402
|
+
if (priority != null)
|
|
2403
|
+
body.priority = priority;
|
|
2404
|
+
if (startDate != null)
|
|
2405
|
+
body.startDateTime = startDate;
|
|
2406
|
+
if (dueDate != null)
|
|
2407
|
+
body.dueDateTime = dueDate;
|
|
2408
|
+
const task = await this.client.createPlannerTask(body);
|
|
2409
|
+
const graphId = task.id;
|
|
2410
|
+
const numericId = hashStringToNumber(graphId);
|
|
2411
|
+
const etag = task['@odata.etag'] ?? '';
|
|
2412
|
+
this.idCache.plannerTasks.set(numericId, { taskId: graphId, etag });
|
|
2413
|
+
return numericId;
|
|
2414
|
+
}
|
|
2415
|
+
/**
|
|
2416
|
+
* Updates a planner task (requires cached ETag).
|
|
2417
|
+
*/
|
|
2418
|
+
async updatePlannerTaskAsync(taskId, updates) {
|
|
2419
|
+
const cached = this.idCache.plannerTasks.get(taskId);
|
|
2420
|
+
if (cached == null)
|
|
2421
|
+
throw new Error(`Task ID ${taskId} not found in cache. Try listing planner tasks first.`);
|
|
2422
|
+
const graphUpdates = {};
|
|
2423
|
+
if (updates.title != null)
|
|
2424
|
+
graphUpdates['title'] = updates.title;
|
|
2425
|
+
if (updates.bucketId != null) {
|
|
2426
|
+
const cachedBucket = this.idCache.plannerBuckets.get(updates.bucketId);
|
|
2427
|
+
if (cachedBucket == null)
|
|
2428
|
+
throw new Error(`Bucket ID ${updates.bucketId} not found in cache. Try listing buckets first.`);
|
|
2429
|
+
graphUpdates['bucketId'] = cachedBucket.bucketId;
|
|
2430
|
+
}
|
|
2431
|
+
if (updates.percentComplete != null)
|
|
2432
|
+
graphUpdates['percentComplete'] = updates.percentComplete;
|
|
2433
|
+
if (updates.priority != null)
|
|
2434
|
+
graphUpdates['priority'] = updates.priority;
|
|
2435
|
+
if (updates.startDate != null)
|
|
2436
|
+
graphUpdates['startDateTime'] = updates.startDate;
|
|
2437
|
+
if (updates.dueDate != null)
|
|
2438
|
+
graphUpdates['dueDateTime'] = updates.dueDate;
|
|
2439
|
+
if (updates.assignments != null)
|
|
2440
|
+
graphUpdates['assignments'] = updates.assignments;
|
|
2441
|
+
const result = await this.client.updatePlannerTask(cached.taskId, graphUpdates, cached.etag);
|
|
2442
|
+
const newEtag = result['@odata.etag'] ?? cached.etag;
|
|
2443
|
+
this.idCache.plannerTasks.set(taskId, { taskId: cached.taskId, etag: newEtag });
|
|
2444
|
+
}
|
|
2445
|
+
/**
|
|
2446
|
+
* Deletes a planner task (requires cached ETag).
|
|
2447
|
+
*/
|
|
2448
|
+
async deletePlannerTaskAsync(taskId) {
|
|
2449
|
+
const cached = this.idCache.plannerTasks.get(taskId);
|
|
2450
|
+
if (cached == null)
|
|
2451
|
+
throw new Error(`Task ID ${taskId} not found in cache. Try listing planner tasks first.`);
|
|
2452
|
+
await this.client.deletePlannerTask(cached.taskId, cached.etag);
|
|
2453
|
+
this.idCache.plannerTasks.delete(taskId);
|
|
2454
|
+
}
|
|
2455
|
+
// ===========================================================================
|
|
2456
|
+
// Planner Task Details
|
|
2457
|
+
// ===========================================================================
|
|
2458
|
+
/**
|
|
2459
|
+
* Gets details for a planner task (description, checklist, references).
|
|
2460
|
+
*/
|
|
2461
|
+
async getPlannerTaskDetailsAsync(taskId) {
|
|
2462
|
+
const cached = this.idCache.plannerTasks.get(taskId);
|
|
2463
|
+
if (cached == null)
|
|
2464
|
+
throw new Error(`Task ID ${taskId} not found in cache. Try listing planner tasks first.`);
|
|
2465
|
+
const details = await this.client.getPlannerTaskDetails(cached.taskId);
|
|
2466
|
+
const etag = details['@odata.etag'] ?? '';
|
|
2467
|
+
this.idCache.plannerTaskDetails.set(taskId, { taskId: cached.taskId, etag });
|
|
2468
|
+
return {
|
|
2469
|
+
id: taskId,
|
|
2470
|
+
description: details.description ?? '',
|
|
2471
|
+
checklist: details.checklist ?? {},
|
|
2472
|
+
references: details.references ?? {},
|
|
2473
|
+
etag,
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2476
|
+
/**
|
|
2477
|
+
* Lists all tasks in a plan with their details (description, checklist, references)
|
|
2478
|
+
* fetched in batched requests. This avoids N+1 queries when you need both the task
|
|
2479
|
+
* list and each task's details.
|
|
2480
|
+
*
|
|
2481
|
+
* Details are fetched via the Graph $batch API (up to 20 per batch).
|
|
2482
|
+
* Partial failures are handled gracefully: tasks whose detail fetch failed will
|
|
2483
|
+
* have `details` set to `undefined`.
|
|
2484
|
+
*/
|
|
2485
|
+
async listPlannerTasksWithDetailsAsync(planId) {
|
|
2486
|
+
// Step 1: List all tasks (populates idCache.plannerTasks)
|
|
2487
|
+
const tasks = await this.listPlannerTasksAsync(planId);
|
|
2488
|
+
if (tasks.length === 0)
|
|
2489
|
+
return [];
|
|
2490
|
+
// Step 2: Build batch requests for each task's details
|
|
2491
|
+
const batchRequests = tasks.map((task) => {
|
|
2492
|
+
const cached = this.idCache.plannerTasks.get(task.id);
|
|
2493
|
+
// cached is guaranteed to exist since listPlannerTasksAsync just populated it
|
|
2494
|
+
const graphTaskId = cached.taskId;
|
|
2495
|
+
return {
|
|
2496
|
+
id: String(task.id),
|
|
2497
|
+
method: 'GET',
|
|
2498
|
+
url: `/planner/tasks/${graphTaskId}/details`,
|
|
2499
|
+
};
|
|
2500
|
+
});
|
|
2501
|
+
// Step 3: Execute batch requests (automatically splits into batches of 20)
|
|
2502
|
+
const batchResults = await this.client.batchRequests(batchRequests);
|
|
2503
|
+
// Step 4: Merge details into tasks
|
|
2504
|
+
return tasks.map((task) => {
|
|
2505
|
+
const result = batchResults.get(String(task.id));
|
|
2506
|
+
if (result != null && result.status >= 200 && result.status < 300) {
|
|
2507
|
+
const detailBody = result.body;
|
|
2508
|
+
const etag = result.headers?.ETag ?? result.headers?.etag ?? '';
|
|
2509
|
+
// Cache the task detail ETag for later updates
|
|
2510
|
+
const cachedTask = this.idCache.plannerTasks.get(task.id);
|
|
2511
|
+
if (cachedTask != null) {
|
|
2512
|
+
this.idCache.plannerTaskDetails.set(task.id, { taskId: cachedTask.taskId, etag });
|
|
2513
|
+
}
|
|
2514
|
+
return {
|
|
2515
|
+
...task,
|
|
2516
|
+
details: {
|
|
2517
|
+
description: detailBody.description ?? '',
|
|
2518
|
+
checklist: detailBody.checklist ?? {},
|
|
2519
|
+
references: detailBody.references ?? {},
|
|
2520
|
+
etag,
|
|
2521
|
+
},
|
|
2522
|
+
};
|
|
2523
|
+
}
|
|
2524
|
+
// Partial failure: return task without details
|
|
2525
|
+
return { ...task, details: undefined };
|
|
2526
|
+
});
|
|
2527
|
+
}
|
|
2528
|
+
/**
|
|
2529
|
+
* Updates details for a planner task (requires cached ETag from getPlannerTaskDetailsAsync).
|
|
2530
|
+
*/
|
|
2531
|
+
async updatePlannerTaskDetailsAsync(taskId, updates) {
|
|
2532
|
+
const cachedTask = this.idCache.plannerTasks.get(taskId);
|
|
2533
|
+
if (cachedTask == null)
|
|
2534
|
+
throw new Error(`Task ID ${taskId} not found in cache. Try listing planner tasks first.`);
|
|
2535
|
+
const cachedDetails = this.idCache.plannerTaskDetails.get(taskId);
|
|
2536
|
+
if (cachedDetails == null)
|
|
2537
|
+
throw new Error(`Task details ETag for task ${taskId} not found in cache. Call get_planner_task_details first.`);
|
|
2538
|
+
const graphUpdates = {};
|
|
2539
|
+
if (updates.description != null)
|
|
2540
|
+
graphUpdates['description'] = updates.description;
|
|
2541
|
+
if (updates.checklist != null)
|
|
2542
|
+
graphUpdates['checklist'] = updates.checklist;
|
|
2543
|
+
if (updates.references != null)
|
|
2544
|
+
graphUpdates['references'] = updates.references;
|
|
2545
|
+
const result = await this.client.updatePlannerTaskDetails(cachedTask.taskId, graphUpdates, cachedDetails.etag);
|
|
2546
|
+
const newEtag = result['@odata.etag'] ?? cachedDetails.etag;
|
|
2547
|
+
this.idCache.plannerTaskDetails.set(taskId, { taskId: cachedTask.taskId, etag: newEtag });
|
|
2548
|
+
}
|
|
2549
|
+
// ===========================================================================
|
|
2550
|
+
// Planner Visualization Data
|
|
2551
|
+
// ===========================================================================
|
|
2552
|
+
/**
|
|
2553
|
+
* Assembles plan, buckets, and tasks into a unified visualization data object.
|
|
2554
|
+
*/
|
|
2555
|
+
async getPlanVisualizationDataAsync(planId) {
|
|
2556
|
+
const plan = await this.getPlanAsync(planId);
|
|
2557
|
+
const buckets = await this.listBucketsAsync(planId);
|
|
2558
|
+
const tasks = await this.listPlannerTasksAsync(planId);
|
|
2559
|
+
return {
|
|
2560
|
+
plan: {
|
|
2561
|
+
id: plan.id,
|
|
2562
|
+
title: plan.title,
|
|
2563
|
+
},
|
|
2564
|
+
buckets: buckets.map(b => ({
|
|
2565
|
+
id: b.id,
|
|
2566
|
+
name: b.name,
|
|
2567
|
+
orderHint: b.orderHint,
|
|
2568
|
+
})),
|
|
2569
|
+
tasks: tasks.map(t => ({
|
|
2570
|
+
id: t.id,
|
|
2571
|
+
title: t.title,
|
|
2572
|
+
bucketId: t.bucketId ?? 0,
|
|
2573
|
+
percentComplete: t.percentComplete,
|
|
2574
|
+
priority: t.priority,
|
|
2575
|
+
startDateTime: t.startDateTime || null,
|
|
2576
|
+
dueDateTime: t.dueDateTime || null,
|
|
2577
|
+
assignments: t.assignees,
|
|
2578
|
+
})),
|
|
2579
|
+
};
|
|
2580
|
+
}
|
|
2581
|
+
// ===========================================================================
|
|
2582
|
+
// Online Meetings
|
|
2583
|
+
// ===========================================================================
|
|
2584
|
+
async listOnlineMeetingsAsync(limit) {
|
|
2585
|
+
const meetings = await this.client.listOnlineMeetings(limit ?? 20);
|
|
2586
|
+
return meetings.map((meeting) => {
|
|
2587
|
+
const graphId = meeting.id ?? '';
|
|
2588
|
+
const numericId = hashStringToNumber(graphId);
|
|
2589
|
+
this.idCache.onlineMeetings.set(numericId, graphId);
|
|
2590
|
+
return {
|
|
2591
|
+
id: numericId,
|
|
2592
|
+
subject: meeting.subject ?? '',
|
|
2593
|
+
startDateTime: meeting.startDateTime ?? '',
|
|
2594
|
+
endDateTime: meeting.endDateTime ?? '',
|
|
2595
|
+
joinUrl: meeting.joinWebUrl ?? '',
|
|
2596
|
+
};
|
|
2597
|
+
});
|
|
2598
|
+
}
|
|
2599
|
+
async getOnlineMeetingAsync(meetingId) {
|
|
2600
|
+
const mapMeeting = (m) => ({
|
|
2601
|
+
id: meetingId,
|
|
2602
|
+
subject: m.subject ?? '',
|
|
2603
|
+
startDateTime: m.startDateTime ?? '',
|
|
2604
|
+
endDateTime: m.endDateTime ?? '',
|
|
2605
|
+
joinUrl: m.joinWebUrl ?? '',
|
|
2606
|
+
participants: m.participants ?? null,
|
|
2607
|
+
});
|
|
2608
|
+
const graphId = this.idCache.onlineMeetings.get(meetingId);
|
|
2609
|
+
if (graphId == null) {
|
|
2610
|
+
// Try to refresh cache
|
|
2611
|
+
await this.listOnlineMeetingsAsync(100);
|
|
2612
|
+
const refreshedId = this.idCache.onlineMeetings.get(meetingId);
|
|
2613
|
+
if (refreshedId == null)
|
|
2614
|
+
return undefined;
|
|
2615
|
+
const meeting = await this.client.getOnlineMeeting(refreshedId);
|
|
2616
|
+
return mapMeeting(meeting);
|
|
2617
|
+
}
|
|
2618
|
+
const meeting = await this.client.getOnlineMeeting(graphId);
|
|
2619
|
+
return mapMeeting(meeting);
|
|
2620
|
+
}
|
|
2621
|
+
async listMeetingRecordingsAsync(meetingId) {
|
|
2622
|
+
const graphMeetingId = this.idCache.onlineMeetings.get(meetingId);
|
|
2623
|
+
if (graphMeetingId == null)
|
|
2624
|
+
throw new Error(`Meeting ID ${meetingId} not found in cache. Try listing online meetings first.`);
|
|
2625
|
+
const recordings = await this.client.listMeetingRecordings(graphMeetingId);
|
|
2626
|
+
return recordings.map((recording) => {
|
|
2627
|
+
const graphId = recording.id ?? '';
|
|
2628
|
+
const numericId = hashStringToNumber(graphId);
|
|
2629
|
+
this.idCache.recordings.set(numericId, { meetingId: graphMeetingId, recordingId: graphId });
|
|
2630
|
+
return {
|
|
2631
|
+
id: numericId,
|
|
2632
|
+
createdDateTime: recording.createdDateTime ?? '',
|
|
2633
|
+
recordingContentUrl: recording.recordingContentUrl ?? '',
|
|
2634
|
+
};
|
|
2635
|
+
});
|
|
2636
|
+
}
|
|
2637
|
+
async downloadMeetingRecordingAsync(recordingId, outputPath) {
|
|
2638
|
+
const cached = this.idCache.recordings.get(recordingId);
|
|
2639
|
+
if (cached == null)
|
|
2640
|
+
throw new Error(`Recording ID ${recordingId} not found in cache. Call list_meeting_recordings first.`);
|
|
2641
|
+
const content = await this.client.getMeetingRecordingContent(cached.meetingId, cached.recordingId);
|
|
2642
|
+
fs.writeFileSync(outputPath, Buffer.from(content));
|
|
2643
|
+
return outputPath;
|
|
2644
|
+
}
|
|
2645
|
+
async listMeetingTranscriptsAsync(meetingId) {
|
|
2646
|
+
const graphMeetingId = this.idCache.onlineMeetings.get(meetingId);
|
|
2647
|
+
if (graphMeetingId == null)
|
|
2648
|
+
throw new Error(`Meeting ID ${meetingId} not found in cache. Try listing online meetings first.`);
|
|
2649
|
+
const transcripts = await this.client.listMeetingTranscripts(graphMeetingId);
|
|
2650
|
+
return transcripts.map((transcript) => {
|
|
2651
|
+
const graphId = transcript.id ?? '';
|
|
2652
|
+
const numericId = hashStringToNumber(graphId);
|
|
2653
|
+
this.idCache.transcripts.set(numericId, { meetingId: graphMeetingId, transcriptId: graphId });
|
|
2654
|
+
return {
|
|
2655
|
+
id: numericId,
|
|
2656
|
+
createdDateTime: transcript.createdDateTime ?? '',
|
|
2657
|
+
contentUrl: transcript.contentUrl ?? '',
|
|
2658
|
+
};
|
|
2659
|
+
});
|
|
2660
|
+
}
|
|
2661
|
+
async getMeetingTranscriptContentAsync(transcriptId, format) {
|
|
2662
|
+
const cached = this.idCache.transcripts.get(transcriptId);
|
|
2663
|
+
if (cached == null)
|
|
2664
|
+
throw new Error(`Transcript ID ${transcriptId} not found in cache. Call list_meeting_transcripts first.`);
|
|
2665
|
+
return await this.client.getMeetingTranscriptContent(cached.meetingId, cached.transcriptId, format ?? 'text/vtt');
|
|
2666
|
+
}
|
|
2667
|
+
// ===========================================================================
|
|
2668
|
+
// Excel Online (Workbook)
|
|
2669
|
+
// ===========================================================================
|
|
2670
|
+
async listWorksheetsAsync(fileId) {
|
|
2671
|
+
const driveItemId = this.idCache.driveItems.get(fileId);
|
|
2672
|
+
if (driveItemId == null)
|
|
2673
|
+
throw new Error(`Drive item ID ${fileId} not found in cache. List OneDrive or SharePoint files first.`);
|
|
2674
|
+
return await this.client.listWorksheets(driveItemId);
|
|
2675
|
+
}
|
|
2676
|
+
async getWorksheetRangeAsync(fileId, worksheetName, range) {
|
|
2677
|
+
const driveItemId = this.idCache.driveItems.get(fileId);
|
|
2678
|
+
if (driveItemId == null)
|
|
2679
|
+
throw new Error(`Drive item ID ${fileId} not found in cache. List OneDrive or SharePoint files first.`);
|
|
2680
|
+
return await this.client.getWorksheetRange(driveItemId, worksheetName, range);
|
|
2681
|
+
}
|
|
2682
|
+
async getUsedRangeAsync(fileId, worksheetName) {
|
|
2683
|
+
const driveItemId = this.idCache.driveItems.get(fileId);
|
|
2684
|
+
if (driveItemId == null)
|
|
2685
|
+
throw new Error(`Drive item ID ${fileId} not found in cache. List OneDrive or SharePoint files first.`);
|
|
2686
|
+
return await this.client.getUsedRange(driveItemId, worksheetName);
|
|
2687
|
+
}
|
|
2688
|
+
async updateWorksheetRangeAsync(fileId, worksheetName, range, values) {
|
|
2689
|
+
const driveItemId = this.idCache.driveItems.get(fileId);
|
|
2690
|
+
if (driveItemId == null)
|
|
2691
|
+
throw new Error(`Drive item ID ${fileId} not found in cache. List OneDrive or SharePoint files first.`);
|
|
2692
|
+
return await this.client.updateWorksheetRange(driveItemId, worksheetName, range, values);
|
|
2693
|
+
}
|
|
2694
|
+
async getTableDataAsync(fileId, tableName) {
|
|
2695
|
+
const driveItemId = this.idCache.driveItems.get(fileId);
|
|
2696
|
+
if (driveItemId == null)
|
|
2697
|
+
throw new Error(`Drive item ID ${fileId} not found in cache. List OneDrive or SharePoint files first.`);
|
|
2698
|
+
return await this.client.getTableData(driveItemId, tableName);
|
|
2699
|
+
}
|
|
2700
|
+
// OneDrive
|
|
2701
|
+
// ===========================================================================
|
|
2702
|
+
/**
|
|
2703
|
+
* Lists files/folders in a drive folder (or root).
|
|
2704
|
+
*/
|
|
2705
|
+
async listDriveItemsAsync(folderId) {
|
|
2706
|
+
let graphFolderId;
|
|
2707
|
+
if (folderId != null) {
|
|
2708
|
+
graphFolderId = this.idCache.driveItems.get(folderId);
|
|
2709
|
+
if (graphFolderId == null)
|
|
2710
|
+
throw new Error(`Drive item ID ${folderId} not found in cache. Try listing drive items first.`);
|
|
2711
|
+
}
|
|
2712
|
+
const items = await this.client.listDriveItems(graphFolderId);
|
|
2713
|
+
return items.map((item) => {
|
|
2714
|
+
const itemId = item.id;
|
|
2715
|
+
const numericId = hashStringToNumber(itemId);
|
|
2716
|
+
this.idCache.driveItems.set(numericId, itemId);
|
|
2717
|
+
return {
|
|
2718
|
+
id: numericId,
|
|
2719
|
+
name: item.name ?? '',
|
|
2720
|
+
size: item.size ?? 0,
|
|
2721
|
+
lastModified: item.lastModifiedDateTime ?? '',
|
|
2722
|
+
isFolder: item.folder != null,
|
|
2723
|
+
webUrl: item.webUrl ?? '',
|
|
2724
|
+
};
|
|
2725
|
+
});
|
|
2726
|
+
}
|
|
2727
|
+
/**
|
|
2728
|
+
* Searches drive items by query.
|
|
2729
|
+
*/
|
|
2730
|
+
async searchDriveItemsAsync(query, limit) {
|
|
2731
|
+
const items = await this.client.searchDriveItems(query, limit);
|
|
2732
|
+
return items.map((item) => {
|
|
2733
|
+
const itemId = item.id;
|
|
2734
|
+
const numericId = hashStringToNumber(itemId);
|
|
2735
|
+
this.idCache.driveItems.set(numericId, itemId);
|
|
2736
|
+
return {
|
|
2737
|
+
id: numericId,
|
|
2738
|
+
name: item.name ?? '',
|
|
2739
|
+
size: item.size ?? 0,
|
|
2740
|
+
lastModified: item.lastModifiedDateTime ?? '',
|
|
2741
|
+
isFolder: item.folder != null,
|
|
2742
|
+
webUrl: item.webUrl ?? '',
|
|
2743
|
+
};
|
|
2744
|
+
});
|
|
2745
|
+
}
|
|
2746
|
+
/**
|
|
2747
|
+
* Gets metadata for a specific drive item.
|
|
2748
|
+
*/
|
|
2749
|
+
async getDriveItemAsync(itemId) {
|
|
2750
|
+
const graphId = this.idCache.driveItems.get(itemId);
|
|
2751
|
+
if (graphId == null)
|
|
2752
|
+
throw new Error(`Drive item ID ${itemId} not found in cache. Try listing drive items first.`);
|
|
2753
|
+
const item = await this.client.getDriveItem(graphId);
|
|
2754
|
+
const fileObj = item.file;
|
|
2755
|
+
const createdByObj = item.createdBy;
|
|
2756
|
+
const createdByUser = createdByObj?.user;
|
|
2757
|
+
return {
|
|
2758
|
+
id: itemId,
|
|
2759
|
+
name: item.name ?? '',
|
|
2760
|
+
size: item.size ?? 0,
|
|
2761
|
+
lastModified: item.lastModifiedDateTime ?? '',
|
|
2762
|
+
isFolder: item.folder != null,
|
|
2763
|
+
webUrl: item.webUrl ?? '',
|
|
2764
|
+
mimeType: fileObj?.mimeType ?? '',
|
|
2765
|
+
createdBy: createdByUser?.displayName ?? '',
|
|
2766
|
+
};
|
|
2767
|
+
}
|
|
2768
|
+
/**
|
|
2769
|
+
* Downloads a drive item to a local file.
|
|
2770
|
+
*/
|
|
2771
|
+
async downloadFileAsync(itemId, outputPath) {
|
|
2772
|
+
const graphId = this.idCache.driveItems.get(itemId);
|
|
2773
|
+
if (graphId == null)
|
|
2774
|
+
throw new Error(`Drive item ID ${itemId} not found in cache. Try listing drive items first.`);
|
|
2775
|
+
const content = await this.client.downloadDriveItem(graphId);
|
|
2776
|
+
const buffer = Buffer.from(content);
|
|
2777
|
+
fs.writeFileSync(outputPath, buffer);
|
|
2778
|
+
return { savedPath: outputPath, size: buffer.length };
|
|
2779
|
+
}
|
|
2780
|
+
/**
|
|
2781
|
+
* Uploads a local file to OneDrive.
|
|
2782
|
+
*/
|
|
2783
|
+
async uploadFileAsync(parentPath, fileName, localFilePath) {
|
|
2784
|
+
const content = fs.readFileSync(localFilePath);
|
|
2785
|
+
const result = await this.client.uploadDriveItem(parentPath, fileName, content);
|
|
2786
|
+
const resultId = result.id;
|
|
2787
|
+
const numericId = hashStringToNumber(resultId);
|
|
2788
|
+
this.idCache.driveItems.set(numericId, resultId);
|
|
2789
|
+
return numericId;
|
|
2790
|
+
}
|
|
2791
|
+
/**
|
|
2792
|
+
* Lists recently accessed drive items.
|
|
2793
|
+
*/
|
|
2794
|
+
async listRecentFilesAsync() {
|
|
2795
|
+
const items = await this.client.listRecentDriveItems();
|
|
2796
|
+
return items.map((item) => {
|
|
2797
|
+
const itemId = item.id;
|
|
2798
|
+
const numericId = hashStringToNumber(itemId);
|
|
2799
|
+
this.idCache.driveItems.set(numericId, itemId);
|
|
2800
|
+
return {
|
|
2801
|
+
id: numericId,
|
|
2802
|
+
name: item.name ?? '',
|
|
2803
|
+
size: item.size ?? 0,
|
|
2804
|
+
lastModified: item.lastModifiedDateTime ?? '',
|
|
2805
|
+
isFolder: item.folder != null,
|
|
2806
|
+
webUrl: item.webUrl ?? '',
|
|
2807
|
+
};
|
|
2808
|
+
});
|
|
2809
|
+
}
|
|
2810
|
+
/**
|
|
2811
|
+
* Lists drive items shared with the user.
|
|
2812
|
+
*/
|
|
2813
|
+
async listSharedWithMeAsync() {
|
|
2814
|
+
const items = await this.client.listSharedWithMe();
|
|
2815
|
+
return items.map((item) => {
|
|
2816
|
+
const itemId = item.id;
|
|
2817
|
+
const numericId = hashStringToNumber(itemId);
|
|
2818
|
+
this.idCache.driveItems.set(numericId, itemId);
|
|
2819
|
+
return {
|
|
2820
|
+
id: numericId,
|
|
2821
|
+
name: item.name ?? '',
|
|
2822
|
+
size: item.size ?? 0,
|
|
2823
|
+
lastModified: item.lastModifiedDateTime ?? '',
|
|
2824
|
+
isFolder: item.folder != null,
|
|
2825
|
+
webUrl: item.webUrl ?? '',
|
|
2826
|
+
};
|
|
2827
|
+
});
|
|
2828
|
+
}
|
|
2829
|
+
/**
|
|
2830
|
+
* Creates a sharing link for a drive item.
|
|
2831
|
+
*/
|
|
2832
|
+
async createSharingLinkAsync(itemId, type, scope) {
|
|
2833
|
+
const graphId = this.idCache.driveItems.get(itemId);
|
|
2834
|
+
if (graphId == null)
|
|
2835
|
+
throw new Error(`Drive item ID ${itemId} not found in cache. Try listing drive items first.`);
|
|
2836
|
+
const result = await this.client.createSharingLink(graphId, type, scope);
|
|
2837
|
+
const link = result.link;
|
|
2838
|
+
return {
|
|
2839
|
+
webUrl: link?.webUrl ?? '',
|
|
2840
|
+
type: link?.type ?? type,
|
|
2841
|
+
scope: link?.scope ?? scope,
|
|
2842
|
+
};
|
|
2843
|
+
}
|
|
2844
|
+
/**
|
|
2845
|
+
* Deletes a drive item.
|
|
2846
|
+
*/
|
|
2847
|
+
async deleteDriveItemAsync(itemId) {
|
|
2848
|
+
const graphId = this.idCache.driveItems.get(itemId);
|
|
2849
|
+
if (graphId == null)
|
|
2850
|
+
throw new Error(`Drive item ID ${itemId} not found in cache. Try listing drive items first.`);
|
|
2851
|
+
await this.client.deleteDriveItem(graphId);
|
|
2852
|
+
this.idCache.driveItems.delete(itemId);
|
|
2853
|
+
}
|
|
2854
|
+
// ===========================================================================
|
|
2855
|
+
// SharePoint Sites & Document Libraries
|
|
2856
|
+
// ===========================================================================
|
|
2857
|
+
/**
|
|
2858
|
+
* Lists followed SharePoint sites, caching IDs.
|
|
2859
|
+
*/
|
|
2860
|
+
async listSitesAsync() {
|
|
2861
|
+
const sites = await this.client.listFollowedSites();
|
|
2862
|
+
const result = [];
|
|
2863
|
+
for (const site of sites) {
|
|
2864
|
+
const siteId = site.id;
|
|
2865
|
+
if (siteId != null) {
|
|
2866
|
+
const numericId = hashStringToNumber(siteId);
|
|
2867
|
+
this.idCache.sites.set(numericId, siteId);
|
|
2868
|
+
result.push({
|
|
2869
|
+
id: numericId,
|
|
2870
|
+
name: site.name ?? '',
|
|
2871
|
+
webUrl: site.webUrl ?? '',
|
|
2872
|
+
displayName: site.displayName ?? '',
|
|
2873
|
+
});
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
return result;
|
|
2877
|
+
}
|
|
2878
|
+
/**
|
|
2879
|
+
* Searches SharePoint sites by keyword, caching IDs.
|
|
2880
|
+
*/
|
|
2881
|
+
async searchSitesAsync(query) {
|
|
2882
|
+
const sites = await this.client.searchSites(query);
|
|
2883
|
+
const result = [];
|
|
2884
|
+
for (const site of sites) {
|
|
2885
|
+
const siteId = site.id;
|
|
2886
|
+
if (siteId != null) {
|
|
2887
|
+
const numericId = hashStringToNumber(siteId);
|
|
2888
|
+
this.idCache.sites.set(numericId, siteId);
|
|
2889
|
+
result.push({
|
|
2890
|
+
id: numericId,
|
|
2891
|
+
name: site.name ?? '',
|
|
2892
|
+
webUrl: site.webUrl ?? '',
|
|
2893
|
+
displayName: site.displayName ?? '',
|
|
2894
|
+
});
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
return result;
|
|
2898
|
+
}
|
|
2899
|
+
/**
|
|
2900
|
+
* Gets details for a specific SharePoint site.
|
|
2901
|
+
*/
|
|
2902
|
+
async getSiteAsync(siteId) {
|
|
2903
|
+
const graphId = this.idCache.sites.get(siteId);
|
|
2904
|
+
if (graphId == null)
|
|
2905
|
+
throw new Error(`Site ID ${siteId} not found in cache. Try listing or searching sites first.`);
|
|
2906
|
+
const site = await this.client.getSite(graphId);
|
|
2907
|
+
return {
|
|
2908
|
+
id: siteId,
|
|
2909
|
+
name: site.name ?? '',
|
|
2910
|
+
webUrl: site.webUrl ?? '',
|
|
2911
|
+
displayName: site.displayName ?? '',
|
|
2912
|
+
description: site.description ?? '',
|
|
2913
|
+
};
|
|
2914
|
+
}
|
|
2915
|
+
/**
|
|
2916
|
+
* Lists document libraries for a SharePoint site, caching IDs.
|
|
2917
|
+
*/
|
|
2918
|
+
async listDocumentLibrariesAsync(siteId) {
|
|
2919
|
+
const graphSiteId = this.idCache.sites.get(siteId);
|
|
2920
|
+
if (graphSiteId == null)
|
|
2921
|
+
throw new Error(`Site ID ${siteId} not found in cache. Try listing or searching sites first.`);
|
|
2922
|
+
const drives = await this.client.listDocumentLibraries(graphSiteId);
|
|
2923
|
+
const result = [];
|
|
2924
|
+
for (const drive of drives) {
|
|
2925
|
+
const driveId = drive.id;
|
|
2926
|
+
if (driveId != null) {
|
|
2927
|
+
const numericId = hashStringToNumber(driveId);
|
|
2928
|
+
this.idCache.documentLibraries.set(numericId, { siteId: graphSiteId, driveId });
|
|
2929
|
+
result.push({
|
|
2930
|
+
id: numericId,
|
|
2931
|
+
name: drive.name ?? '',
|
|
2932
|
+
webUrl: drive.webUrl ?? '',
|
|
2933
|
+
driveType: drive.driveType ?? '',
|
|
2934
|
+
});
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
return result;
|
|
2938
|
+
}
|
|
2939
|
+
/**
|
|
2940
|
+
* Lists items in a document library or folder, caching IDs.
|
|
2941
|
+
*/
|
|
2942
|
+
async listLibraryItemsAsync(libraryId, folderId) {
|
|
2943
|
+
const libCached = this.idCache.documentLibraries.get(libraryId);
|
|
2944
|
+
if (libCached == null)
|
|
2945
|
+
throw new Error(`Library ID ${libraryId} not found in cache. Try listing document libraries first.`);
|
|
2946
|
+
let folderItemId;
|
|
2947
|
+
if (folderId != null) {
|
|
2948
|
+
const folderCached = this.idCache.libraryDriveItems.get(folderId);
|
|
2949
|
+
if (folderCached == null)
|
|
2950
|
+
throw new Error(`Folder ID ${folderId} not found in cache. Try listing library items first.`);
|
|
2951
|
+
folderItemId = folderCached.itemId;
|
|
2952
|
+
}
|
|
2953
|
+
const items = await this.client.listLibraryItems(libCached.driveId, folderItemId);
|
|
2954
|
+
const result = [];
|
|
2955
|
+
for (const item of items) {
|
|
2956
|
+
const itemGraphId = item.id;
|
|
2957
|
+
if (itemGraphId != null) {
|
|
2958
|
+
const numericId = hashStringToNumber(itemGraphId);
|
|
2959
|
+
this.idCache.libraryDriveItems.set(numericId, { driveId: libCached.driveId, itemId: itemGraphId });
|
|
2960
|
+
result.push({
|
|
2961
|
+
id: numericId,
|
|
2962
|
+
name: item.name ?? '',
|
|
2963
|
+
size: item.size ?? 0,
|
|
2964
|
+
webUrl: item.webUrl ?? '',
|
|
2965
|
+
lastModifiedDateTime: item.lastModifiedDateTime ?? '',
|
|
2966
|
+
isFolder: item.folder != null,
|
|
2967
|
+
});
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
return result;
|
|
2971
|
+
}
|
|
2972
|
+
/**
|
|
2973
|
+
* Downloads a file from a document library to the specified path.
|
|
2974
|
+
*/
|
|
2975
|
+
async downloadLibraryFileAsync(itemId, outputPath) {
|
|
2976
|
+
const cached = this.idCache.libraryDriveItems.get(itemId);
|
|
2977
|
+
if (cached == null)
|
|
2978
|
+
throw new Error(`Item ID ${itemId} not found in cache. Try listing library items first.`);
|
|
2979
|
+
const content = await this.client.downloadLibraryFile(cached.driveId, cached.itemId);
|
|
2980
|
+
const resolvedPath = path.resolve(outputPath);
|
|
2981
|
+
const dir = path.dirname(resolvedPath);
|
|
2982
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
2983
|
+
fs.writeFileSync(resolvedPath, Buffer.from(content));
|
|
2984
|
+
return resolvedPath;
|
|
2985
|
+
}
|
|
2986
|
+
/**
|
|
2987
|
+
* Gets the Graph string ID for a folder from the cache.
|
|
2988
|
+
*/
|
|
2989
|
+
getFolderGraphId(folderId) {
|
|
2990
|
+
return this.idCache.folders.get(folderId);
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
/**
|
|
2994
|
+
* Creates a Microsoft Graph API repository.
|
|
2995
|
+
*/
|
|
2996
|
+
export function createGraphRepository(deviceCodeCallback) {
|
|
2997
|
+
return new GraphRepository(deviceCodeCallback);
|
|
2998
|
+
}
|
|
2999
|
+
//# sourceMappingURL=repository.js.map
|