| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import type { MSTeamsAccessTokenProvider } from "./attachments/types.js"; |
|
|
| const GRAPH_ROOT = "https://graph.microsoft.com/v1.0"; |
| const GRAPH_BETA = "https://graph.microsoft.com/beta"; |
| const GRAPH_SCOPE = "https://graph.microsoft.com"; |
|
|
| export interface OneDriveUploadResult { |
| id: string; |
| webUrl: string; |
| name: string; |
| } |
|
|
| function parseUploadedDriveItem( |
| data: { id?: string; webUrl?: string; name?: string }, |
| label: "OneDrive" | "SharePoint", |
| ): OneDriveUploadResult { |
| if (!data.id || !data.webUrl || !data.name) { |
| throw new Error(`${label} upload response missing required fields`); |
| } |
|
|
| return { |
| id: data.id, |
| webUrl: data.webUrl, |
| name: data.name, |
| }; |
| } |
|
|
| async function uploadDriveItem(params: { |
| buffer: Buffer; |
| filename: string; |
| contentType?: string; |
| tokenProvider: MSTeamsAccessTokenProvider; |
| fetchFn?: typeof fetch; |
| url: string; |
| label: "OneDrive" | "SharePoint"; |
| }): Promise<OneDriveUploadResult> { |
| const fetchFn = params.fetchFn ?? fetch; |
| const token = await params.tokenProvider.getAccessToken(GRAPH_SCOPE); |
|
|
| const res = await fetchFn(params.url, { |
| method: "PUT", |
| headers: { |
| Authorization: `Bearer ${token}`, |
| "Content-Type": params.contentType ?? "application/octet-stream", |
| }, |
| body: new Uint8Array(params.buffer), |
| }); |
|
|
| if (!res.ok) { |
| const body = await res.text().catch(() => ""); |
| throw new Error(`${params.label} upload failed: ${res.status} ${res.statusText} - ${body}`); |
| } |
|
|
| return parseUploadedDriveItem( |
| (await res.json()) as { id?: string; webUrl?: string; name?: string }, |
| params.label, |
| ); |
| } |
|
|
| |
| |
| |
| |
| export async function uploadToOneDrive(params: { |
| buffer: Buffer; |
| filename: string; |
| contentType?: string; |
| tokenProvider: MSTeamsAccessTokenProvider; |
| fetchFn?: typeof fetch; |
| }): Promise<OneDriveUploadResult> { |
| |
| const uploadPath = `/OpenClawShared/${encodeURIComponent(params.filename)}`; |
| return await uploadDriveItem({ |
| ...params, |
| url: `${GRAPH_ROOT}/me/drive/root:${uploadPath}:/content`, |
| label: "OneDrive", |
| }); |
| } |
|
|
| export interface OneDriveSharingLink { |
| webUrl: string; |
| } |
|
|
| |
| |
| |
| |
| export async function createSharingLink(params: { |
| itemId: string; |
| tokenProvider: MSTeamsAccessTokenProvider; |
| /** Sharing scope: "organization" (default) or "anonymous" */ |
| scope?: "organization" | "anonymous"; |
| fetchFn?: typeof fetch; |
| }): Promise<OneDriveSharingLink> { |
| const fetchFn = params.fetchFn ?? fetch; |
| const token = await params.tokenProvider.getAccessToken(GRAPH_SCOPE); |
|
|
| const res = await fetchFn(`${GRAPH_ROOT}/me/drive/items/${params.itemId}/createLink`, { |
| method: "POST", |
| headers: { |
| Authorization: `Bearer ${token}`, |
| "Content-Type": "application/json", |
| }, |
| body: JSON.stringify({ |
| type: "view", |
| scope: params.scope ?? "organization", |
| }), |
| }); |
|
|
| if (!res.ok) { |
| const body = await res.text().catch(() => ""); |
| throw new Error(`Create sharing link failed: ${res.status} ${res.statusText} - ${body}`); |
| } |
|
|
| const data = (await res.json()) as { |
| link?: { webUrl?: string }; |
| }; |
|
|
| if (!data.link?.webUrl) { |
| throw new Error("Create sharing link response missing webUrl"); |
| } |
|
|
| return { |
| webUrl: data.link.webUrl, |
| }; |
| } |
|
|
| |
| |
| |
| |
| export async function uploadAndShareOneDrive(params: { |
| buffer: Buffer; |
| filename: string; |
| contentType?: string; |
| tokenProvider: MSTeamsAccessTokenProvider; |
| scope?: "organization" | "anonymous"; |
| fetchFn?: typeof fetch; |
| }): Promise<{ |
| itemId: string; |
| webUrl: string; |
| shareUrl: string; |
| name: string; |
| }> { |
| const uploaded = await uploadToOneDrive({ |
| buffer: params.buffer, |
| filename: params.filename, |
| contentType: params.contentType, |
| tokenProvider: params.tokenProvider, |
| fetchFn: params.fetchFn, |
| }); |
|
|
| const shareLink = await createSharingLink({ |
| itemId: uploaded.id, |
| tokenProvider: params.tokenProvider, |
| scope: params.scope, |
| fetchFn: params.fetchFn, |
| }); |
|
|
| return { |
| itemId: uploaded.id, |
| webUrl: uploaded.webUrl, |
| shareUrl: shareLink.webUrl, |
| name: uploaded.name, |
| }; |
| } |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| export async function uploadToSharePoint(params: { |
| buffer: Buffer; |
| filename: string; |
| contentType?: string; |
| tokenProvider: MSTeamsAccessTokenProvider; |
| siteId: string; |
| fetchFn?: typeof fetch; |
| }): Promise<OneDriveUploadResult> { |
| |
| const uploadPath = `/OpenClawShared/${encodeURIComponent(params.filename)}`; |
| return await uploadDriveItem({ |
| ...params, |
| url: `${GRAPH_ROOT}/sites/${params.siteId}/drive/root:${uploadPath}:/content`, |
| label: "SharePoint", |
| }); |
| } |
|
|
| export interface ChatMember { |
| aadObjectId: string; |
| displayName?: string; |
| } |
|
|
| |
| |
| |
| |
| export interface DriveItemProperties { |
| |
| eTag: string; |
| |
| webDavUrl: string; |
| |
| name: string; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export async function getDriveItemProperties(params: { |
| siteId: string; |
| itemId: string; |
| tokenProvider: MSTeamsAccessTokenProvider; |
| fetchFn?: typeof fetch; |
| }): Promise<DriveItemProperties> { |
| const fetchFn = params.fetchFn ?? fetch; |
| const token = await params.tokenProvider.getAccessToken(GRAPH_SCOPE); |
|
|
| const res = await fetchFn( |
| `${GRAPH_ROOT}/sites/${params.siteId}/drive/items/${params.itemId}?$select=eTag,webDavUrl,name`, |
| { headers: { Authorization: `Bearer ${token}` } }, |
| ); |
|
|
| if (!res.ok) { |
| const body = await res.text().catch(() => ""); |
| throw new Error(`Get driveItem properties failed: ${res.status} ${res.statusText} - ${body}`); |
| } |
|
|
| const data = (await res.json()) as { |
| eTag?: string; |
| webDavUrl?: string; |
| name?: string; |
| }; |
|
|
| if (!data.eTag || !data.webDavUrl || !data.name) { |
| throw new Error("DriveItem response missing required properties (eTag, webDavUrl, or name)"); |
| } |
|
|
| return { |
| eTag: data.eTag, |
| webDavUrl: data.webDavUrl, |
| name: data.name, |
| }; |
| } |
|
|
| |
| |
| |
| |
| export async function getChatMembers(params: { |
| chatId: string; |
| tokenProvider: MSTeamsAccessTokenProvider; |
| fetchFn?: typeof fetch; |
| }): Promise<ChatMember[]> { |
| const fetchFn = params.fetchFn ?? fetch; |
| const token = await params.tokenProvider.getAccessToken(GRAPH_SCOPE); |
|
|
| const res = await fetchFn(`${GRAPH_ROOT}/chats/${params.chatId}/members`, { |
| headers: { Authorization: `Bearer ${token}` }, |
| }); |
|
|
| if (!res.ok) { |
| const body = await res.text().catch(() => ""); |
| throw new Error(`Get chat members failed: ${res.status} ${res.statusText} - ${body}`); |
| } |
|
|
| const data = (await res.json()) as { |
| value?: Array<{ |
| userId?: string; |
| displayName?: string; |
| }>; |
| }; |
|
|
| return (data.value ?? []) |
| .map((m) => ({ |
| aadObjectId: m.userId ?? "", |
| displayName: m.displayName, |
| })) |
| .filter((m) => m.aadObjectId); |
| } |
|
|
| |
| |
| |
| |
| |
| export async function createSharePointSharingLink(params: { |
| siteId: string; |
| itemId: string; |
| tokenProvider: MSTeamsAccessTokenProvider; |
| /** Sharing scope: "organization" (default) or "users" (per-user with recipients) */ |
| scope?: "organization" | "users"; |
| /** Required when scope is "users": AAD object IDs of recipients */ |
| recipientObjectIds?: string[]; |
| fetchFn?: typeof fetch; |
| }): Promise<OneDriveSharingLink> { |
| const fetchFn = params.fetchFn ?? fetch; |
| const token = await params.tokenProvider.getAccessToken(GRAPH_SCOPE); |
| const scope = params.scope ?? "organization"; |
|
|
| |
| const apiRoot = scope === "users" ? GRAPH_BETA : GRAPH_ROOT; |
|
|
| const body: Record<string, unknown> = { |
| type: "view", |
| scope: scope === "users" ? "users" : "organization", |
| }; |
|
|
| |
| if (scope === "users" && params.recipientObjectIds?.length) { |
| body.recipients = params.recipientObjectIds.map((id) => ({ objectId: id })); |
| } |
|
|
| const res = await fetchFn( |
| `${apiRoot}/sites/${params.siteId}/drive/items/${params.itemId}/createLink`, |
| { |
| method: "POST", |
| headers: { |
| Authorization: `Bearer ${token}`, |
| "Content-Type": "application/json", |
| }, |
| body: JSON.stringify(body), |
| }, |
| ); |
|
|
| if (!res.ok) { |
| const respBody = await res.text().catch(() => ""); |
| throw new Error( |
| `Create SharePoint sharing link failed: ${res.status} ${res.statusText} - ${respBody}`, |
| ); |
| } |
|
|
| const data = (await res.json()) as { |
| link?: { webUrl?: string }; |
| }; |
|
|
| if (!data.link?.webUrl) { |
| throw new Error("Create SharePoint sharing link response missing webUrl"); |
| } |
|
|
| return { |
| webUrl: data.link.webUrl, |
| }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export async function uploadAndShareSharePoint(params: { |
| buffer: Buffer; |
| filename: string; |
| contentType?: string; |
| tokenProvider: MSTeamsAccessTokenProvider; |
| siteId: string; |
| chatId?: string; |
| usePerUserSharing?: boolean; |
| fetchFn?: typeof fetch; |
| }): Promise<{ |
| itemId: string; |
| webUrl: string; |
| shareUrl: string; |
| name: string; |
| }> { |
| |
| const uploaded = await uploadToSharePoint({ |
| buffer: params.buffer, |
| filename: params.filename, |
| contentType: params.contentType, |
| tokenProvider: params.tokenProvider, |
| siteId: params.siteId, |
| fetchFn: params.fetchFn, |
| }); |
|
|
| |
| let scope: "organization" | "users" = "organization"; |
| let recipientObjectIds: string[] | undefined; |
|
|
| if (params.usePerUserSharing && params.chatId) { |
| try { |
| const members = await getChatMembers({ |
| chatId: params.chatId, |
| tokenProvider: params.tokenProvider, |
| fetchFn: params.fetchFn, |
| }); |
|
|
| if (members.length > 0) { |
| scope = "users"; |
| recipientObjectIds = members.map((m) => m.aadObjectId); |
| } |
| } catch { |
| |
| |
| } |
| } |
|
|
| |
| const shareLink = await createSharePointSharingLink({ |
| siteId: params.siteId, |
| itemId: uploaded.id, |
| tokenProvider: params.tokenProvider, |
| scope, |
| recipientObjectIds, |
| fetchFn: params.fetchFn, |
| }); |
|
|
| return { |
| itemId: uploaded.id, |
| webUrl: uploaded.webUrl, |
| shareUrl: shareLink.webUrl, |
| name: uploaded.name, |
| }; |
| } |
|
|