Specialized functionality for Microsoft Teams including task modules, channel management, and Teams-specific event handling.
Extended bot worker with Teams-specific functionality.
/**
* Specialized version of BotWorker with additional methods for Microsoft Teams
*/
class TeamsBotWorker extends BotWorker {
/** Grants access to the TeamsInfo helper class */
teams: TeamsInfo;
/**
* Reply to a Teams task module task/fetch or task/submit with a task module response
* @param message The incoming message
* @param taskInfo An object in the form {type, value}
*/
replyWithTaskInfo(message: BotkitMessage, taskInfo: any): Promise<any>;
}Usage Examples:
// Handle task module fetch
controller.on("task/fetch", async (bot, message) => {
const taskInfo = {
type: "continue",
value: {
title: "Survey Form",
height: 400,
width: 500,
url: "https://myapp.com/survey"
}
};
await bot.replyWithTaskInfo(message, taskInfo);
});
// Handle task module submit
controller.on("task/submit", async (bot, message) => {
console.log("Task module data:", message.value);
// Process the submitted data
const result = await processTaskData(message.value);
// Return response
await bot.replyWithTaskInfo(message, {
type: "message",
value: "Thanks for your submission!"
});
});
// Access Teams API
controller.on("message", async (bot, message) => {
// Get team members
const members = await bot.teams.getTeamMembers(bot.getConfig("context"));
console.log(`Team has ${members.length} members`);
});Extended Bot Framework adapter with Teams-specific features.
/**
* Extended BotFrameworkAdapter with Teams support
*/
class BotkitBotFrameworkAdapter extends BotFrameworkAdapter {
/** Custom bot worker class for Teams */
botkit_worker: TeamsBotWorker;
/**
* Get the list of channels in a MS Teams team
* @param context A TurnContext object from a team conversation
* @returns Array of channels in format [{name: string, id: string}]
*/
getChannels(context: TurnContext): Promise<ChannelInfo[]>;
/**
* Create connector client with custom user agent
* @param serviceUrl Clients service url
*/
createConnectorClient(serviceUrl: string): ConnectorClient;
}Usage Examples:
// Create Teams-enabled controller
const controller = new Botkit({
adapter: new BotkitBotFrameworkAdapter({
appId: process.env.MICROSOFT_APP_ID,
appPassword: process.env.MICROSOFT_APP_PASSWORD
})
});
// Get team channels
controller.on("message", async (bot, message) => {
if (message.channelId === "msteams") {
const channels = await bot.getConfig("adapter").getChannels(bot.getConfig("context"));
const channelList = channels.map(ch => `- ${ch.name}`).join("\n");
await bot.reply(message, `Available channels:\n${channelList}`);
}
});Middleware for processing Teams-specific invoke events.
/**
* Middleware that emits special events for Teams "invokes"
*/
class TeamsInvokeMiddleware extends MiddlewareSet {
/**
* Process Teams invoke events and set appropriate event types
* @param context Turn context
* @param next Next middleware function
*/
onTurn(context: TurnContext, next: () => Promise<any>): Promise<void>;
}Usage Examples:
import { TeamsInvokeMiddleware } from "botkit";
// Add Teams middleware to adapter
const adapter = new BotFrameworkAdapter(adapterConfig);
adapter.use(new TeamsInvokeMiddleware());
const controller = new Botkit({ adapter });
// Now you can handle Teams-specific events
controller.on("task/fetch", async (bot, message) => {
// Handle task module fetch
});
controller.on("task/submit", async (bot, message) => {
// Handle task module submit
});
controller.on("composeExtension/query", async (bot, message) => {
// Handle compose extension query
});Handle Teams task modules for rich interactive experiences.
// Task module fetch - show a form or webpage
controller.on("task/fetch", async (bot, message) => {
const taskInfo = {
type: "continue",
value: {
title: "User Information",
height: 300,
width: 400,
card: {
type: "AdaptiveCard",
version: "1.0",
body: [
{
type: "TextBlock",
text: "Enter your details"
},
{
type: "Input.Text",
id: "name",
placeholder: "Your name"
}
],
actions: [
{
type: "Action.Submit",
title: "Submit"
}
]
}
}
};
await bot.replyWithTaskInfo(message, taskInfo);
});
// Task module submit - process form data
controller.on("task/submit", async (bot, message) => {
const userData = message.value;
// Process the data
await saveUserData(userData);
// Return success message
await bot.replyWithTaskInfo(message, {
type: "message",
value: `Hello ${userData.name}! Your information has been saved.`
});
});Handle Teams compose extensions (messaging extensions).
// Handle compose extension queries
controller.on("composeExtension/query", async (bot, message) => {
const query = message.value.commandId;
const searchText = message.value.parameters?.[0]?.value || "";
let results = [];
if (query === "searchUsers") {
results = await searchUsers(searchText);
}
// Return results
await bot.reply(message, {
composeExtension: {
type: "result",
attachmentLayout: "list",
attachments: results.map(user => ({
contentType: "application/vnd.microsoft.card.thumbnail",
content: {
title: user.name,
subtitle: user.email,
text: user.department
}
}))
}
});
});
// Handle compose extension submissions
controller.on("composeExtension/submitAction", async (bot, message) => {
const action = message.value;
// Process the action
const result = await processAction(action);
await bot.reply(message, {
composeExtension: {
type: "result",
attachmentLayout: "list",
attachments: [result]
}
});
});Handle file consent and card action events.
// Handle file consent
controller.on("fileConsent/invoke", async (bot, message) => {
const fileInfo = message.value;
if (fileInfo.action === "accept") {
// Upload file to the accepted location
await uploadFile(fileInfo.uploadInfo.uploadUrl, fileData);
await bot.reply(message, "File uploaded successfully!");
} else {
await bot.reply(message, "File upload was declined.");
}
});
// Handle card actions
controller.on("cardAction", async (bot, message) => {
const action = message.value;
switch (action.verb) {
case "approve":
await handleApproval(action.data);
await bot.reply(message, "Request approved!");
break;
case "reject":
await handleRejection(action.data);
await bot.reply(message, "Request rejected.");
break;
}
});Work with Teams channels and conversations.
// Get team channels
controller.hears("list channels", "message", async (bot, message) => {
try {
const channels = await bot.getConfig("adapter").getChannels(bot.getConfig("context"));
const channelList = channels.map(ch =>
`• **${ch.name || "General"}** (${ch.id})`
).join("\n");
await bot.reply(message, `Team channels:\n${channelList}`);
} catch (error) {
await bot.reply(message, "Could not retrieve channels. Make sure this is a team conversation.");
}
});
// Send message to specific channel
controller.hears("announce", "message", async (bot, message) => {
const channels = await bot.getConfig("adapter").getChannels(bot.getConfig("context"));
const generalChannel = channels.find(ch => ch.name === "General");
if (generalChannel) {
const announcement = {
type: "message",
text: "📢 Important announcement for the team!",
conversation: { id: generalChannel.id }
};
await bot.say(announcement);
}
});Access team and conversation member details.
// Get team members
controller.hears("team members", "message", async (bot, message) => {
const context = bot.getConfig("context");
try {
const members = await bot.teams.getTeamMembers(context);
const memberList = members.map(member =>
`• ${member.name} (${member.email})`
).join("\n");
await bot.reply(message, `Team members:\n${memberList}`);
} catch (error) {
await bot.reply(message, "Unable to retrieve team members.");
}
});
// Get conversation members (for group chats)
controller.hears("chat members", "message", async (bot, message) => {
const context = bot.getConfig("context");
try {
const members = await bot.teams.getMembers(context);
const memberCount = members.length;
await bot.reply(message, `This conversation has ${memberCount} members.`);
} catch (error) {
await bot.reply(message, "Unable to retrieve conversation members.");
}
});Send rich Adaptive Cards in Teams.
controller.hears("show card", "message", async (bot, message) => {
const card = {
type: "message",
attachments: [{
contentType: "application/vnd.microsoft.card.adaptive",
content: {
type: "AdaptiveCard",
version: "1.2",
body: [
{
type: "TextBlock",
text: "Poll Question",
weight: "Bolder",
size: "Medium"
},
{
type: "TextBlock",
text: "What's your favorite programming language?",
wrap: true
}
],
actions: [
{
type: "Action.Submit",
title: "JavaScript",
data: { vote: "javascript" }
},
{
type: "Action.Submit",
title: "Python",
data: { vote: "python" }
},
{
type: "Action.Submit",
title: "TypeScript",
data: { vote: "typescript" }
}
]
}
}]
};
await bot.reply(message, card);
});
// Handle card submissions
controller.on("cardAction", async (bot, message) => {
const vote = message.value.data.vote;
await recordVote(vote);
await bot.reply(message, `Thanks for voting for ${vote}!`);
});Send proactive messages to Teams users and channels.
// Save conversation reference for later use
let savedReferences = {};
controller.on("conversationUpdate", async (bot, message) => {
if (message.membersAdded) {
// Save reference for proactive messaging
savedReferences[message.from.id] = message.reference;
}
});
// Send proactive message
async function sendProactiveMessage(userId, messageText) {
const reference = savedReferences[userId];
if (reference) {
const bot = await controller.spawn();
await bot.changeContext(reference);
await bot.say(messageText);
}
}
// Example: Send daily reminder
setInterval(async () => {
for (const userId in savedReferences) {
await sendProactiveMessage(userId, "Daily reminder: Don't forget to submit your timesheet!");
}
}, 24 * 60 * 60 * 1000); // Once per day