import { resolveMicrosoftIntegration, type MicrosoftAccountParams } from './accounts.ts'
import { graphItems, graphRequest, requireConfirmOrDryRun, type JsonObject } from './core.ts'
import { boundedTop } from './validation.ts'
export type TeamsListParams = MicrosoftAccountParams & {
top?: number
}
export type TeamsChatMessagesParams = MicrosoftAccountParams & {
chatId: string
top?: number
}
export type TeamsSendChatParams = MicrosoftAccountParams & {
chatId: string
content: string
contentType?: 'text' | 'html'
dryRun?: boolean
confirm?: boolean
}
export type TeamsChannelsParams = MicrosoftAccountParams & {
teamId: string
top?: number
}
export type TeamsChannelMessagesParams = MicrosoftAccountParams & {
teamId: string
channelId: string
top?: number
}
export type TeamsSendChannelParams = MicrosoftAccountParams & {
teamId: string
channelId: string
content: string
contentType?: 'text' | 'html'
dryRun?: boolean
confirm?: boolean
}
/** List Teams the signed-in user has joined. */
export async function listJoinedTeams(params: TeamsListParams = {}) {
const data = await graphRequest<JsonObject>({
integration: resolveMicrosoftIntegration(params),
path: '/me/joinedTeams',
query: {
$top: boundedTop(params.top, 20, 100),
$select: 'id,displayName,description,isArchived,visibility',
},
})
return graphItems<JsonObject>(data)
}
/** List 1:1 and group chats available to the signed-in user. */
export async function listChats(params: TeamsListParams = {}) {
const data = await graphRequest<JsonObject>({
integration: resolveMicrosoftIntegration(params),
path: '/me/chats',
query: {
$top: boundedTop(params.top, 20, 50),
$select: 'id,topic,chatType,createdDateTime,lastUpdatedDateTime,webUrl',
},
})
return graphItems<JsonObject>(data)
}
/** List messages in a Teams chat. */
export async function listChatMessages(params: TeamsChatMessagesParams) {
const chatId = params.chatId?.trim()
if (!chatId) throw new Error('listChatMessages requires chatId.')
const data = await graphRequest<JsonObject>({
integration: resolveMicrosoftIntegration(params),
path: '/me/chats/' + encodeURIComponent(chatId) + '/messages',
query: {
$top: boundedTop(params.top, 20, 50),
$select: 'id,createdDateTime,from,body,importance,webUrl',
},
})
return graphItems<JsonObject>(data)
}
/** Preview or send a Teams chat message. Sending requires confirm: true. */
export async function sendChatMessage(params: TeamsSendChatParams) {
const chatId = params.chatId?.trim()
if (!chatId) throw new Error('sendChatMessage requires chatId.')
const content = params.content
if (typeof content !== 'string' || content.trim().length === 0) {
throw new Error('sendChatMessage requires content.')
}
const path = '/me/chats/' + encodeURIComponent(chatId) + '/messages'
const body = {
body: {
contentType: params.contentType === 'html' ? 'html' : 'text',
content,
},
}
const mode = requireConfirmOrDryRun({
dryRun: params.dryRun,
confirm: params.confirm,
action: 'sendChatMessage',
})
if (mode.dryRun) {
return { dryRun: true as const, method: 'POST', path, body }
}
return graphRequest<JsonObject>({
integration: resolveMicrosoftIntegration(params),
method: 'POST',
path,
body,
})
}
/** List channels in a team. */
export async function listChannels(params: TeamsChannelsParams) {
const teamId = params.teamId?.trim()
if (!teamId) throw new Error('listChannels requires teamId.')
const data = await graphRequest<JsonObject>({
integration: resolveMicrosoftIntegration(params),
path: '/teams/' + encodeURIComponent(teamId) + '/channels',
query: {
$top: boundedTop(params.top, 20, 100),
$select: 'id,displayName,description,membershipType,webUrl',
},
})
return graphItems<JsonObject>(data)
}
/** Preview or send a Teams channel message. Needs ChannelMessage.Send. Sending requires confirm: true. */
export async function sendChannelMessage(params: TeamsSendChannelParams) {
const teamId = params.teamId?.trim()
const channelId = params.channelId?.trim()
if (!teamId) throw new Error('sendChannelMessage requires teamId.')
if (!channelId) throw new Error('sendChannelMessage requires channelId.')
const content = params.content
if (typeof content !== 'string' || content.trim().length === 0) {
throw new Error('sendChannelMessage requires content.')
}
const path =
'/teams/' +
encodeURIComponent(teamId) +
'/channels/' +
encodeURIComponent(channelId) +
'/messages'
const body = {
body: {
contentType: params.contentType === 'html' ? 'html' : 'text',
content,
},
}
const mode = requireConfirmOrDryRun({
dryRun: params.dryRun,
confirm: params.confirm,
action: 'sendChannelMessage',
})
if (mode.dryRun) {
return { dryRun: true as const, method: 'POST', path, body }
}
return graphRequest<JsonObject>({
integration: resolveMicrosoftIntegration(params),
method: 'POST',
path,
body,
})
}
/** List messages in a team channel. Often needs admin-consented ChannelMessage.Read.All. */
export async function listChannelMessages(params: TeamsChannelMessagesParams) {
const teamId = params.teamId?.trim()
const channelId = params.channelId?.trim()
if (!teamId) throw new Error('listChannelMessages requires teamId.')
if (!channelId) throw new Error('listChannelMessages requires channelId.')
const data = await graphRequest<JsonObject>({
integration: resolveMicrosoftIntegration(params),
path:
'/teams/' +
encodeURIComponent(teamId) +
'/channels/' +
encodeURIComponent(channelId) +
'/messages',
query: {
$top: boundedTop(params.top, 20, 50),
$select: 'id,createdDateTime,from,body,importance,webUrl',
},
})
return graphItems<JsonObject>(data)
}
/**
* Microsoft Teams helpers for Microsoft Graph.
* @example
* import teams from 'kody:@kody/microsoft/teams'
* const { items } = await teams().listJoinedTeams()
*/
export default function teams() {
return {
listJoinedTeams,
listChats,
listChatMessages,
sendChatMessage,
listChannels,
listChannelMessages,
sendChannelMessage,
}
}