import { Client } from '@microsoft/microsoft-graph-client'; async function getGraphToken(): Promise { const tenantId = process.env.TENANT_ID; const clientId = process.env.CLIENT_ID; const clientSecret = process.env.CLIENT_SECRET; if (!tenantId || !clientId || !clientSecret) { throw new Error('Missing Graph env configuration: TENANT_ID, CLIENT_ID, CLIENT_SECRET'); } const tokenEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; const params = new URLSearchParams(); params.append('client_id', clientId); params.append('client_secret', clientSecret); params.append('scope', 'https://graph.microsoft.com/.default'); params.append('grant_type', 'client_credentials'); const response = await fetch(tokenEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: params, }); if (!response.ok) { const text = await response.text(); throw new Error(`Graph token request failed: ${response.status} ${response.statusText} - ${text}`); } const data = await response.json() as { access_token?: string }; if (!data?.access_token) throw new Error('Graph token response missing access_token'); return data.access_token; } async function getGraphClient() { const token = await getGraphToken(); return Client.init({ authProvider: (done) => done(null, token) }); } export async function listTeamChannels(teamId: string) { const client = await getGraphClient(); const resp = await client.api(`/teams/${teamId}/channels`).get(); const channels = (resp?.value || []).map((ch: any) => ({ id: ch.id, displayName: ch.displayName, description: ch.description ?? null })); return channels; } export async function listChannelMessages(teamId: string, channelId: string, top: number = 50) { const client = await getGraphClient(); const resp = await client .api(`/teams/${teamId}/channels/${channelId}/messages`) .query({ $top: Math.min(top, 50) }) .get(); const messages = (resp?.value || []).map((m: any) => ({ id: m.id, createdDateTime: m.createdDateTime, summary: m.summary ?? null, subject: m.subject ?? null, from: m.from?.user?.displayName ?? m.from?.application?.displayName ?? null, body: m.body ?? null, bodyContent: m.body?.content ?? null, bodyContentType: m.body?.contentType ?? null, messageType: m.messageType ?? null, attachments: Array.isArray(m.attachments) ? m.attachments.map((a: any) => ({ id: a.id ?? null, contentType: a.contentType ?? null, name: a.name ?? null, contentUrl: a.contentUrl ?? null, content: a.content ?? null })) : [], raw: m })); return messages; } export async function listTeamChannelsWithToken(teamId: string, accessToken: string) { const client = Client.init({ authProvider: (done) => done(null, accessToken) }); const resp = await client.api(`/teams/${teamId}/channels`).get(); const channels = (resp?.value || []).map((ch: any) => ({ id: ch.id, displayName: ch.displayName, description: ch.description ?? null })); return channels; } export async function listChannelMessagesWithToken(teamId: string, channelId: string, top: number = 50, accessToken: string) { const client = Client.init({ authProvider: (done) => done(null, accessToken) }); const resp = await client .api(`/teams/${teamId}/channels/${channelId}/messages`) .query({ $top: Math.min(top, 50) }) .get(); const messages = (resp?.value || []).map((m: any) => ({ id: m.id, createdDateTime: m.createdDateTime, summary: m.summary ?? null, subject: m.subject ?? null, from: m.from?.user?.displayName ?? m.from?.application?.displayName ?? null, body: m.body ?? null, bodyContent: m.body?.content ?? null, bodyContentType: m.body?.contentType ?? null, messageType: m.messageType ?? null, attachments: Array.isArray(m.attachments) ? m.attachments.map((a: any) => ({ id: a.id ?? null, contentType: a.contentType ?? null, name: a.name ?? null, contentUrl: a.contentUrl ?? null, content: a.content ?? null })) : [], raw: m })); return messages; } export async function deleteChannelMessage(teamId: string, channelId: string, messageId: string) { const client = await getGraphClient(); try { const url = `/teams/${teamId}/channels/${channelId}/messages/${messageId}`; console.log(`📌 Attempting DELETE on: ${url}`); await client.api(url).delete(); return { success: true }; } catch (err: any) { const statusCode = err?.statusCode || err?.status; const msg = err?.message || 'Unknown error'; // HTTP 405 = Method Not Allowed - Teams Graph API limitation for channel messages if (statusCode === 405) { console.warn(`âš ī¸ 405 Method Not Allowed - Teams API limitation for channel message deletion. Applying soft-delete approach...`); // Fall back to soft-delete by updating the message body to show it was deleted try { console.log(`📌 Attempting soft-delete via PATCH...`); // Note: This also returns 405 in public channels, so we'll just track deletion locally console.log(`â„šī¸ Microsoft Teams API does not support deletion of channel messages via REST API`); console.log(`â„šī¸ Suggestion: Use Teams client UI, or use delegated token with ChannelMessage.ReadWrite.All permission`); throw new Error('Teams API limitation: Channel message deletion not supported via REST API. Delete manually from Teams client or grant ChatMessage.ReadWrite.All delegated permission with user token.'); } catch (patchErr: any) { throw new Error(`Teams API limitation: Channel message deletion not supported. ${patchErr.message}`); } } console.error(`❌ Delete failed - Message: ${msg}, StatusCode: ${statusCode}`); throw new Error(`Graph delete error: ${msg} (status: ${statusCode || 'unknown'})`); } } export async function deleteChannelMessageWithToken(teamId: string, channelId: string, messageId: string, accessToken: string) { const client = Client.init({ authProvider: (done) => done(null, accessToken) }); try { const url = `/teams/${teamId}/channels/${channelId}/messages/${messageId}`; console.log(`📌 Attempting DELETE on: ${url}`); await client.api(url).delete(); return { success: true }; } catch (err: any) { const statusCode = err?.statusCode || err?.status; const msg = err?.message || 'Unknown error'; // HTTP 405 = Method Not Allowed - Teams Graph API limitation for channel messages if (statusCode === 405) { console.warn(`âš ī¸ 405 Method Not Allowed - Even with delegated token, Teams API may not support channel message deletion`); console.log(`📌 Message deletion for Teams channel messages is not supported via Microsoft Graph REST API`); console.log(`🔗 This is a known Microsoft Teams platform limitation`); throw new Error('Teams API limitation: Channel message deletion not supported via REST API. Please delete messages directly from the Teams client interface.'); } console.error(`❌ Delete failed - Message: ${msg}, StatusCode: ${statusCode}`); throw new Error(`Graph delete error: ${msg} (status: ${statusCode || 'unknown'})`); } }