Skip to content
This repository was archived by the owner on Jan 5, 2026. It is now read-only.
Original file line number Diff line number Diff line change
@@ -1,45 +1,130 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

const { TeamsActivityHandler, CardFactory } = require('botbuilder');
const { TeamsActivityHandler, CardFactory, TeamsInfo } = require('botbuilder');

class TeamsMessagingExtensionsActionBot extends TeamsActivityHandler {
handleTeamsMessagingExtensionSubmitAction(context, action) {
switch (action.commandId) {
case 'createCard':
return createCardCommand(context, action);
case 'shareMessage':
return shareMessageCommand(context, action);
default:
throw new Error('NotImplemented');
case 'createCard':
return createCardCommand(context, action);
case 'shareMessage':
return shareMessageCommand(context, action);
default:
throw new Error('NotImplemented');
}
}

async handleTeamsMessagingExtensionFetchTask(context, action) {
try {
const member = await this.getSingleMember(context);
return {
task: {
type: 'continue',
value: {
card: GetAdaptiveCardAttachment(),
height: 400,
title: 'Hello ' + member,
width: 300
},
},
};
} catch (e) {
if (e.code === 'BotNotInConversationRoster') {
return {
task: {
type: 'continue',
value: {
card: GetJustInTimeCardAttachment(),
height: 400,
title: 'Adaptive Card - App Installation',
width: 300
},
},
};
}
throw e;
}

}
async getSingleMember(context) {
try {
const member = await TeamsInfo.getMember(
context,
context.activity.from.id
);
return member.name;
} catch (e) {
if (e.code === 'MemberNotFoundInConversation') {
context.sendActivity(MessageFactory.text('Member not found.'));
return e.code;
}
throw e;
}
}
}

function GetJustInTimeCardAttachment() {
return CardFactory.adaptiveCard({
actions: [
{
type: 'Action.Submit',
title: 'Continue',
data: { msteams: { justInTimeInstall: true } }
},
],
body: [
{
text: 'Looks like you have not used Action Messaging Extension app in this team/chat. Please click **Continue** to add this app.',
type: 'TextBlock',
wrap: 'bolder'
},
],
type: 'AdaptiveCard',
version: '1.0',
});
}

function GetAdaptiveCardAttachment() {
return CardFactory.adaptiveCard({
actions: [{ type: 'Action.Submit', title: 'Close' }],
body: [
{
text: 'This app is installed in this conversation. You can now use it to do some great stuff!!!',
type: 'TextBlock',
isSubtle: false,
warp: true
},
],
type: 'AdaptiveCard',
version: '1.0',
});
}

function createCardCommand(context, action) {
// The user has chosen to create a card by choosing the 'Create Card' context menu command.
const data = action.data;
const heroCard = CardFactory.heroCard(data.title, data.text);
heroCard.content.subtitle = data.subTitle;
const attachment = { contentType: heroCard.contentType, content: heroCard.content, preview: heroCard };
const attachment = {
contentType: heroCard.contentType,
content: heroCard.content,
preview: heroCard,
};

return {
composeExtension: {
type: 'result',
attachmentLayout: 'list',
attachments: [
attachment
]
}
attachments: [attachment]
},
};
}

function shareMessageCommand(context, action) {
// The user has chosen to share a message by choosing the 'Share Message' context menu command.
let userName = 'unknown';
if (action.messagePayload.from &&
action.messagePayload.from.user &&
action.messagePayload.from.user.displayName) {
if (action.messagePayload?.from?.user?.displayName) {
userName = action.messagePayload.from.user.displayName;
}

Expand All @@ -48,28 +133,34 @@ function shareMessageCommand(context, action) {
let images = [];
const includeImage = action.data.includeImage;
if (includeImage === 'true') {
images = ['https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU'];
images = [
'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU',
];
}
const heroCard = CardFactory.heroCard(`${ userName } originally sent this message:`,
const heroCard = CardFactory.heroCard(
`${userName} originally sent this message:`,
action.messagePayload.body.content,
images);
images
);

if (action.messagePayload.attachments && action.messagePayload.attachments.length > 0) {
if (action.messagePayload.attachments?.length > 0) {
// This sample does not add the MessagePayload Attachments. This is left as an
// exercise for the user.
heroCard.content.subtitle = `(${ action.messagePayload.attachments.length } Attachments not included)`;
heroCard.content.subtitle = `(${action.messagePayload.attachments.length} Attachments not included)`;
}

const attachment = { contentType: heroCard.contentType, content: heroCard.content, preview: heroCard };
const attachment = {
contentType: heroCard.contentType,
content: heroCard.content,
preview: heroCard
};

return {
composeExtension: {
type: 'result',
attachmentLayout: 'list',
attachments: [
attachment
]
}
attachments: [attachment]
},
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,28 @@
"color": "icon-color.png"
},
"accentColor": "#FFFFFF",
"bots": [
{
"botId": "<<YOUR-MICROSOFT-APP-ID>>",
"needsChannelSelector": false,
"isNotificationOnly": false,
"scopes": [
"team",
"personal",
"groupchat"
]
}
],
"composeExtensions": [
{
"botId": "<<YOUR-MICROSOFT-APP-ID>>",
"commands": [
{
"id": "createCard",
"type": "action",
"context": [ "compose" ],
"context": [
"compose"
],
"description": "Command to run action to create a Card from Compose Box",
"title": "Create Card",
"parameters": [
Expand All @@ -57,7 +71,9 @@
{
"id": "shareMessage",
"type": "action",
"context": [ "message" ],
"context": [
"message"
],
"description": "Test command to run action on message context (message sharing)",
"title": "Share Message",
"parameters": [
Expand All @@ -68,6 +84,16 @@
"inputType": "toggle"
}
]
},
{
"id": "FetchRoster",
"description": "Fetch the conversation roster",
"title": "FetchRoster",
"type": "action",
"fetchTask": true,
"context": [
"compose"
]
}
]
}
Expand Down