How to check if bot/application is uninstalled from ms teams? - botframework

I have a bot application which I installed in my team, using team scope. Now when this application is uninstalled from team is there any event I can get/monitor?
I was going over the Microsoft documentation and tried following piece of code. According to this onTeamsMembersRemovedEvent is called when bot or a member is removed.
export class MyBot extends TeamsActivityHandler {
constructor() {
super();
this.onTeamsMembersRemovedEvent(async (membersRemoved: ChannelAccount[], teamInfo: TeamInfo, turnContext: TurnContext, next: () => Promise<void>): Promise<void> => {
let removedMembers: string = '';
console.log(JSON.stringify(membersRemoved));
membersRemoved.forEach((account) => {
removedMembers += account.id + ' ';
});
const name = !teamInfo ? 'not in team' : teamInfo.name;
const card = CardFactory.heroCard('Account Removed', `${removedMembers} removed from ${teamInfo.name}.`);
const message = MessageFactory.attachment(card);
await turnContext.sendActivity(message);
await next();
});
}
}
But for me I do not receive this event when I remove the bot application. I remove the bot application manually from inside the apps by choosing uninstall.

When uninstalled the app by navigating to More options > Manage teams > Apps > Uninstall , teamMemberRemoved event got fired and got correct response.
{
"membersRemoved": [
{
"id": "28:aXXXXX04-e293-447c-951f-6a6971b3b66b"
}
],
"type": "conversationUpdate",
"timestamp": "2021-10-19T15:24:45.9499395Z",
"id": "f:e0d2c276-XXXXX-5d74-73ad-3c67b9b0ae4f",
"channelId": "msteams",
"serviceUrl": "https://smba.trafficmanager.net/amer/",
"from": {
"id": "29:1qanOqiaR5gWE-aWoYPdYjB--mUmmVQFGddHxyb37WXc4FI-eD62pSxBJYoXXXXXeGyFlpiTzRd-fTCiBmNbeuQg",
"aadObjectId": "XXXXXc4d0-XXXXX-4154-a85f-a89cd77aefa8"
},
"conversation": {
"isGroup": true,
"conversationType": "channel",
"tenantId": "3XXXXXef-XXXXX-4d60-XXXXX-0aXXXXX693df",
"id": "19:XXXXX53a099498f9e08679e58f1f7fc#thread.tacv2"
},
"recipient": {
"id": "28:aXXXXX-e293-XXXXX-951f-6a6971b3b66b",
"name": "XXXXX"
},
"channelData": {
"team": {
"aadGroupId": "XXXXXf3-fa01-XXXXX-bb62-201225dce9e4",
"name": "XXXXX",
"id": "19:XXXXX099498f9e08679eXXXXXf7fc#thread.tacv2"
},
"eventType": "teamMemberRemoved",
"tenant": {
"id": "36a708ef-XXXX-4d60-9de0-XXXXXXdf"
}
}}

Related

UpdateActivity throws 400 "Unknown activity type"

I do have a Bot that is reachable via MS Teams. The bot sends an Adaptive Card with some Text and a submit-action. When the user clicks on this submit-action, I want to proceed the input and then update the prior sent Adaptive card via calling context.updateActivity. According to documentation, I can use activity.Id = turnContext.Activity.ReplyToId; to specify the message I want to update. But the call of context.updateActivity results in a 400 HTTP error, the message is "Unknown activity type".
Some investigation:
This error occurs when I want to send another Adaptive Card and when I want to send plain text
I verified, that the id of sendActivity is the same as turnContext.Activity.ReplyToId
Any idea?
Here is my code:
Adaptive Card
{
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"body": [
{
"type": "TextBlock",
"text": "Some text",
"wrap": true
},
{
"type": "Input.ChoiceSet",
"id": "Feedback",
"value": "",
"style": "compact",
"placeholder": "Wie hilfreich war diese Antwort?",
"choices": [
{
"title": "⭐",
"value": "1"
},
{
"title": "⭐⭐",
"value": "2"
},
{
"title": "⭐⭐⭐",
"value": "3"
},
{
"title": "⭐⭐⭐⭐",
"value": "4"
},
{
"title": "⭐⭐⭐⭐⭐",
"value": "5"
}
]
}
],
"actions": [
{
"title": "Feedback absenden",
"type": "Action.Submit"
}
]
}
Sending the message:
private handleMessage = async (context: TurnContext, next: () => Promise<void>): Promise<void> => {
const adaptiveCard = AdaptiveCardFactory.createAdaptiveCardFromTemplateAndData(AnswerWithFeedbackCard);
const result = await context.sendActivity({ attachments: [adaptiveCard] });
console.error("send msg with id " + result?.id);
}
code to update the message:
private handleMessage = async (context: TurnContext, next: () => Promise<void>): Promise<void> => {
console.error("received msg with id " + context.activity.replyToId);
if (context.activity.value && !context.activity.text) {
const updatedCard = CardFactory.adaptiveCard(this.botConfig.updatedCard);
await context.updateActivity({ text: "updated :)", id: context.activity.replyToId});
//or
await context.updateActivity({ attachments: [updatedCard], id: context.activity.replyToId});
}
}
Got it!
const att = AdaptiveCardFactory.createAdaptiveCardFromTemplateAndData(AnswerWithAnsweredFeedbackCard, {
answer: "Mir geht's super, danke der Nachfrage!",
starsCount: context.activity.value.Feedback
});
const id = await context.updateActivity( { attachments: [ att ], id: context.activity.replyToId} );
This does not work, but this does:
const att = AdaptiveCardFactory.createAdaptiveCardFromTemplateAndData(AnswerWithAnsweredFeedbackCard, {
answer: "Mir geht's super, danke der Nachfrage!",
starsCount: context.activity.value.Feedback
});
const msg = MessageFactory.attachment( att )
msg.id = context.activity.replyToId;
const id = await context.updateActivity( msg )
So, you need to save the save the sent msg in a variable and set the id of this variable instead of using the "inplace"-implementation of updateActivity

not receiving name of the group in activity object of groupChat conversation update. MSBot framework for Teams (nodejs)

When someone add my bot to their group chat in the activity object of conversationUpdate event I am not receiving the name of the group chat.
{
"membersAdded": [
{
"id": "28:306a0c33-333-47ce-a9f8-03799e676dc2"
}
],
"type": "conversationUpdate",
"timestamp": "2020-05-14T19:55:28.0039315Z",
"id": "f:9476c223-fd49-7a31-06b9-d97ff3fd4d76",
"channelId": "msteams",
"serviceUrl": "https://smba.trafficmanager.net/in/",
"from": {
"id": "29:1q1sbfe3sRPvYJgi-PwHztKmrnLj7ozY233ciqF2CRitV0cOgY5ldNxWtJMDVMmXYuItSHM5xETWmlyuZvoEGXg",
"aadObjectId": "d1ac8255-7079-4bff-bda6-1593f42e7d52"
},
"conversation": {
"isGroup": true,
"conversationType": "groupChat",
"tenantId": "86b9a961-9303-4cc1-b78d-62d07f6ab178",
"id": "19:29da1fbb91314556a8bba82eaf17ea61#thread.v2"
},
"recipient": {
"id": "28:306a0c33-333-47ce-a9f8-03799e676dc2",
"name": "testbot"
},
"channelData": {
"tenant": {
"id": "86b9a961-9303-4cc1-b78d-62d07f6ab178"
}
}
}
is there any way I can get the name of the groupChat ?
#Nikhil, You do not get the Team name in member added event.Team name will be passed only in case of team rename event. Please check the documentation for more details.

Creating a custom adapter in Botframework for Actions on Google

I'm currently writing a custom adapter in Typescript to connect Google Assistant to Microsoft's Botframework. In this adapter I'm attempting to capture the Google Assistant conversation object through a webook call and change it using my bot.
At this moment the only thing that my bot is doing is receive the request from Actions on Google and parsing the request body into an ActionsOnGoogleConversation object. After this I call conv.ask() to try a simple conversation between the two services.
Api Endpoint:
app.post("/api/google", (req, res) => {
googleAdapter.processActivity(req, res, async (context) => {
await bot.run(context);
});
});
Adapter processActivity function:
public async processActivity(req: WebRequest, res: WebResponse, logic: (context: TurnContext) => Promise<void>): Promise<void> {
const body = req.body;
let conv = new ActionsSdkConversation();
Object.assign(conv, body);
res.status(200);
res.send(conv.ask("Boo"));
};
When I try to start the conversation I get the following error in the Action on Google console.
UnparseableJsonResponse
API Version 2: Failed to parse JSON response string with
'INVALID_ARGUMENT' error: "availableSurfaces: Cannot find field." HTTP
Status Code: 200.
I've already checked the response and I can find a field called availableSurfaces in the AoG console and when I call my bot using Postman.
Response:
{
"responses": [
"Boo"
],
"expectUserResponse": true,
"digested": false,
"noInputs": [],
"speechBiasing": [],
"_responded": true,
"_ordersv3": false,
"request": {},
"headers": {},
"_init": {},
"sandbox": false,
"input": {},
"surface": {
"capabilities": [
{
"name": "actions.capability.MEDIA_RESPONSE_AUDIO"
},
{
"name": "actions.capability.AUDIO_OUTPUT"
},
{
"name": "actions.capability.ACCOUNT_LINKING"
},
{
"name": "actions.capability.SCREEN_OUTPUT"
}
]
},
"available": {
"surfaces": {
"list": [],
"capabilities": {
"surfaces": []
}
}
},
"user": {
"locale": "en-US",
"lastSeen": "2019-11-14T12:40:52Z",
"userStorage": "{\"data\":{\"userId\":\"c1a4b8ab-06bb-4270-80f5-958cfdff57bd\"}}",
"userVerificationStatus": "VERIFIED"
},
"arguments": {
"parsed": {
"input": {},
"list": []
},
"status": {
"input": {},
"list": []
},
"raw": {
"list": [],
"input": {}
}
},
"device": {},
"screen": false,
"body": {},
"version": 2,
"action": "",
"intent": "",
"parameters": {},
"contexts": {
"input": {},
"output": {}
},
"incoming": {
"parsed": []
},
"query": "",
"data": {},
"conversation": {
"conversationId": "ABwppHEky66Iy1-qJ_4g08i3Z1HNHe2aDTrVTqY4otnNmdOgY2CC0VDbyt9lIM-_WkJA8emxbMPVxS5uutYHW2BzRQ",
"type": "NEW"
},
"inputs": [
{
"intent": "actions.intent.MAIN",
"rawInputs": [
{
"inputType": "VOICE",
"query": "Talk to My test app"
}
]
}
],
"availableSurfaces": [
{
"capabilities": [
{
"name": "actions.capability.AUDIO_OUTPUT"
},
{
"name": "actions.capability.SCREEN_OUTPUT"
},
{
"name": "actions.capability.WEB_BROWSER"
}
]
}
]
}
Does anyone know what might be causing this? I personally feel that creating the ActionsSdkConversation could be the cause, but I've not found any examples of using Google Assistant without getting the conv object from the standard intent handeling setup.
So I managed to fix it by changing the approach, instead of having an API point that fits the structure of bot framework I changed it to the intenthandler of AoG.
Google controller
export class GoogleController {
public endpoint: GoogleEndpoint;
private adapter: GoogleAssistantAdapter;
private bot: SampleBot;
constructor(bot: SampleBot) {
this.bot = bot;
this.adapter = new GoogleAssistantAdapter();
this.endpoint = actionssdk();
this.setupIntents(this.endpoint);
};
private setupIntents(endpoint: GoogleEndpoint) {
endpoint.intent(GoogleIntentTypes.Start, (conv: ActionsSdkConversation) => {
this.sendMessageToBotFramework(conv);
});
endpoint.intent(GoogleIntentTypes.Text, conv => {
this.sendMessageToBotFramework(conv);
});
};
private sendMessageToBotFramework(conv: ActionsSdkConversation) {
this.adapter.processActivity(conv, async (context) => {
await this.bot.run(context);
});
};
};
interface GoogleEndpoint extends OmniHandler, BaseApp , ActionsSdkApp <{}, {}, ActionsSdkConversation<{}, {}>> {};
Once the conv object was in the adapter, I used the conv object to create an activity which the bot used to do its things and saved it in state using context.turnState()
Adapter ProcessActivity
public async processActivity(conv: ActionsSdkConversation, logic: (context: TurnContext) => Promise<void>): Promise<ActionsSdkConversation> {
const activty = this.createActivityFromGoogleConversation(conv);
const context = this.createContext(activty);
context.turnState.set("httpBody", conv);
await this.runMiddleware(context, logic);
const result = context.turnState.get("httpBody");
return result;
};
Bot
export class SampleBot extends ActivityHandler {
constructor() {
super();
this.onMessage(async (context, next) => {
await context.sendActivity(`You said: ${context.activity.text}`);
await next();
});
}
Once the bot send a response, I used the result to modify the conv object, save it and then return it in processActivity().
private createGoogleConversationFromActivity(activity: Partial<Activity>, context: TurnContext) {
const conv = context.turnState.get("httpBody");
if (activity.speak) {
const response = new SimpleResponse({
text: activity.text,
speech: activity.speak
});
conv.ask(response);
} else {
if (!activity.text) {
throw Error("Activity text cannot be undefined");
};
conv.ask(activity.text);
};
context.turnState.set("httpBody", conv);
return;
};
That resulted into a simple conversation between Google Assistant and Bot Framework.

How do I get notifySuccess to send verification code?

I am following this sample to implement Microsoft Graph interaction. I have managed to follow and display a page with my verification code, but the Teams SDK doesn't seem to want to post the code to my bot automatically.
I have the following lines in my oauth success page, as per the sample
<script src="https://unpkg.com/#microsoft/teams-js#1.4.2/dist/MicrosoftTeams.min.js" integrity="sha384-DcoNUuQTjpW5Sw3msonn/2ojgtNvtC5fCGd5U9RUpL3g1hla1LzHz8I9YIYSXe6q" crossorigin="anonymous"></script>
<script type="text/javascript">
setTimeout(function () {
document.getElementById("instructionText").style.display = "initial";
}, 5000);
microsoftTeams.initialize();
microsoftTeams.authentication.notifySuccess("{{verificationCode}}");
</script>
According to the sample, the bot receives an invoke message with name = signin/verifyState, but I do not. The sign-in page does close though.
Any ideas on why the page will not notify my bot of the code automatically?
Sorry this took so long. Got it.
So, the issue is that you're looking for a message activity, but Teams is sending an invoke activity, which isn't caught in onMessage. Instead, in your BotController, use something like:
this.onUnrecognizedActivityType(async (turnContext, next) => {
console.log(`GOT : ${ JSON.stringify(turnContext.activity, null, 2)}`)
});
This produces:
GOT : {
"name": "signin/verifyState",
"type": "invoke",
"timestamp": "2019-06-07T22:19:53.810Z",
"localTimestamp": "2019-06-07T22:19:53.810Z",
"id": "f:490708197841asdfasdfasdf15",
"channelId": "msteams",
"serviceUrl": "https://smba.trafficmanager.net/amer/",
"from": {
"id": "29:1AvIMwjQc1iBRcFYBe-0opf4YzVU130fNasdjkhfkldjshfjkahgsdfjklasdkjfasdfcS_7MO9DKFFNL6Ow",
"name": "asdfasdfasdfasdf",
"aadObjectId": "asdfasdfasdfasdf"
},
"conversation": {
"conversationType": "personal",
"tenantId": "asdfasdfasdfasdfasd",
"id": "asdfasdfasdfasdf"
},
"recipient": {
"id": "asdfasdfasdfasdf",
"name": "asdfasdfasdfasdf"
},
"entities": [
{
"locale": "en-US",
"country": "US",
"platform": "Windows",
"type": "clientInfo"
}
],
"channelData": {
"tenant": {
"id": "asdfasdfadsfasdf"
},
"source": {
"name": "message"
}
},
"value": {
"state": "asdfasdfasdfasdf"
},
"locale": "en-US"
}

How to handle tap action on adaptive card bot framework v4?

I am having an adaptive card which will be displayed in waterfall dialog. I want to know is it possible to capture the tap action on the adaptive card I don't want to add button to handle click. I am using v4 version of bot framework.
Tap dialog is triggered twice
My adaptive card :
var Card1 = {
"type": "AdaptiveCard",
"selectAction": {
"type": "Action.Submit",
"id": "tap",
"title": "tap",
"data": "data": { tap: 'tap' }
},
"backgroundImage": "https://download-ssl.msgamestudios.com/content/mgs/ce/production/SolitaireWin10/dev/adapative_card_assets/v1/card_background.png",
"body": [
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"items": [
{
"type": "Image",
"url": "https://download-ssl.msgamestudios.com/content/mgs/ce/production/SolitaireWin10/dev/adapative_card_assets/v1/tile_spider.png",
"size": "Stretch"
}
],
"width": 1
},
{
"type": "Column",
"items": [
{
"type": "TextBlock",
"horizontalAlignment": "Center",
"weight": "Bolder",
"color": "Light",
"text": "Click here to play another game of Spider in Microsoft Solitaire Collection!",
"wrap": true
}
],
"width": 1
}
]
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "0.5"
};
This is how i have updated the code
Code
// Add prompts that will be used by the main dialogs.
this.dialogs.add(new TextPrompt(TAP_PROMPT, () => true));
// Create a dialog that asks the user for their name.
this.dialogs.add(new WaterfallDialog(PUBLICTRANS, [
this.promptForTap.bind(this),
this.captureTap.bind(this)
]));
async promptForTap(step) {
var Card1 = {
"type": "AdaptiveCard",
"selectAction": {
"type": "Action.Submit",
"id": "tap",
"title": "tap",
"data": "data": { tap: 'tap' }
},
"backgroundImage": "https://download-ssl.msgamestudios.com/content/mgs/ce/production/SolitaireWin10/dev/adapative_card_assets/v1/card_background.png",
"body": [
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"items": [
{
"type": "Image",
"url": "https://download-ssl.msgamestudios.com/content/mgs/ce/production/SolitaireWin10/dev/adapative_card_assets/v1/tile_spider.png",
"size": "Stretch"
}
],
"width": 1
},
{
"type": "Column",
"items": [
{
"type": "TextBlock",
"horizontalAlignment": "Center",
"weight": "Bolder",
"color": "Light",
"text": "Click here to play another game of Spider in Microsoft Solitaire Collection!",
"wrap": true
}
],
"width": 1
}
]
}
],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.0"
};
const reply = {
attachments: [CardFactory.adaptiveCard(Card1)]
};
return await step.prompt(TAP_PROMPT, { prompt: reply });
}
async captureTap(step) {
// Edited from original answer
await step.context.sendActivity(`You selected `);
// Send Back Channel Event
await step.context.sendActivity({ type: 'event', name: 'tapEvent' });
return await step.endDialog();
}
output the card is triggered twice
You can send a Back Channel Event to WebChat from the Bot when the user clicks on an AdaptiveCard to trigger an event on the web page.
Simple AdaptiveCard with Select Action
First, create an AdaptiveCard that has a selectAction. Note, you can either add a selectAction to the whole card or different components in the card like an image or a column. When the user clicks on a part of the card that has a selectAction, it will send an activity to the Bot that contains the data attribute from the action that we can use to determine which action was triggered on the bot side.
Be sure to set the title attribute, the data field, and the type which should be Action.Submit.
{
"type": "AdaptiveCard",
"selectAction": {
"type": "Action.Submit",
"id": "tap",
"title": "tap",
"data": { "tap": "Play again"}
},
"body": [
{
"type": "TextBlock",
"horizontalAlignment": "Center",
"size": "Medium",
"weight": "Bolder",
"text": "Click Me!"
},
{
"type": "Image",
"url": "https://usercontent2.hubstatic.com/13896379.jpg"
}],
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.0"
}
Bot Code - Node Js
Constructor
const TAP_PROMPT = 'tap_prompt';
constructor(conversationState, userState) {
...
// Add prompts that will be used by the main dialogs.
this.dialogs.add(new TextPrompt(TAP_PROMPT, () => true));
// Create a dialog that asks the user for their name.
this.dialogs.add(new WaterfallDialog(WHO_ARE_YOU, [
this.promptForTap.bind(this),
this.captureTap.bind(this)
]));
}
AdaptiveCard Prompt
In one of the steps of your Waterfall, you can create a reply object that has the AdaptiveCard as an attachment. Then you can pass that reply to the prompt. I would recommend using a TextPrompt for this step.
// This step in the dialog sends the user an adaptive card with a selectAction
async promptForTap(step) {
const reply = {
attachments: [CardFactory.adaptiveCard(Card)]
}
return await step.prompt(TAP_PROMPT, { prompt: reply });
}
Capture AdaptiveCard selecteAction
In the next step of the Waterfall, you can get the data attribute sent from the AdaptiveCard by accessing step.value. Here, send an activity to the bot with a type and a name property. These two attributes will be used to filter incoming activities in WebChat and trigger the proper event.
// This step captures the tap and sends a back channel event to WebChat
async captureTap(step) {
// Edited from original answer
await step.context.sendActivity(`You selected ${step.context.activity.value.tap}`);
// Send Back Channel Event
await step.context.sendActivity({type: 'event', name: 'tapEvent'});
return await step.endDialog();
}
WebChat Middleware
In WebChat, we are going to create a custom middleware to check incoming activities. When we encounter an activity that has a name and type that we recognize, trigger your event on the webpage. In the example below, I just alerted the use that they clicked on the AdaptiveCard.
const store = window.WebChat.createStore(
{},
({ dispatch }) => next => action => {
if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
const { name, type } = action.payload.activity;
if (type === 'event' && name === 'tapEvent') {
alert("You tapped on the AdaptiveCard.");
}
}
return next(action);
}
);
window.WebChat.renderWebChat({
directLine: window.WebChat.createDirectLine({ token }),
store,
}, document.getElementById('webchat'));
For more details on back channel events and creating a custom middleware in WebChat, checkout this sample in the WebChat Repo.
Hope this helps!

Resources