Skip to content

Commit 9105d5c

Browse files
authored
Merge pull request #1032 from microsoft/johtaylo/atmention
add at mention removal code and used it in inspection
2 parents c4998e1 + b2dc0eb commit 9105d5c

File tree

4 files changed

+187
-1
lines changed

4 files changed

+187
-1
lines changed

libraries/botbuilder-core/src/turnContext.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* Copyright (c) Microsoft Corporation. All rights reserved.
66
* Licensed under the MIT License.
77
*/
8-
import { Activity, ActivityTypes, ConversationReference, InputHints, ResourceResponse } from 'botframework-schema';
8+
import { Activity, ActivityTypes, ConversationReference, InputHints, ResourceResponse, Mention } from 'botframework-schema';
99
import { BotAdapter } from './botAdapter';
1010
import { shallowCopy } from './internal';
1111

@@ -79,6 +79,70 @@ export class TurnContext {
7979
}
8080
}
8181

82+
/**
83+
* Rewrites the activity text without any at mention.
84+
*
85+
* @remarks
86+
* Some channels, for example Microsoft Teams, add at mention details into the text on a message activity.
87+
* This can interfer with later procsesing. This is a helper function to remove the at mention.
88+
*
89+
* ```JavaScript
90+
* const updatedText = TurnContext.removeRecipientMention(context.request);
91+
* ```
92+
* @param activity The activity to alter the text on
93+
*/
94+
public static removeRecipientMention(activity: Partial<Activity>): string {
95+
return TurnContext.removeMentionText(activity, activity.recipient.id);
96+
}
97+
98+
/**
99+
* Rewrites the activity text without any at mention. Specifying a particular recipient id.
100+
*
101+
* @remarks
102+
* Some channels, for example Microsoft Teams, add at mention details into the text on a message activity.
103+
* This can interfer with later procsesing. This is a helper function to remove the at mention.
104+
*
105+
* ```JavaScript
106+
* const updatedText = TurnContext.removeRecipientMention(context.request);
107+
* ```
108+
* @param activity The activity to alter the text on
109+
* @param id The recipient id of the at mention
110+
*/
111+
public static removeMentionText(activity: Partial<Activity>, id: string): string {
112+
var mentions = TurnContext.getMentions(activity);
113+
for (var i=0; i<mentions.length; i++) {
114+
if (mentions[i].mentioned.id === id) {
115+
var mentionNameMatch = mentions[i].text.match(/(?<=<at.*>)(.*?)(?=<\/at>)/i);
116+
if (mentionNameMatch.length > 0) {
117+
activity.text = activity.text.replace(mentionNameMatch[0], '');
118+
activity.text = activity.text.replace(/<at><\/at>/g, '');
119+
}
120+
}
121+
}
122+
return activity.text;
123+
}
124+
125+
/**
126+
* Returns the mentions on an activity.
127+
*
128+
* ```JavaScript
129+
* const mentions = TurnContext.getMentions(context.request);
130+
* ```
131+
* @param activity The activity to alter the text on
132+
* @param id The recipient id of the at mention
133+
*/
134+
public static getMentions(activity: Partial<Activity>): Mention[] {
135+
var result: Mention[] = [];
136+
if (activity.entities !== undefined) {
137+
for (var i=0; i<activity.entities.length; i++) {
138+
if (activity.entities[i].type.toLowerCase() === 'mention') {
139+
result.push(activity.entities[i] as Mention);
140+
}
141+
}
142+
}
143+
return result;
144+
}
145+
82146
/**
83147
* Returns the conversation reference for an activity.
84148
*

libraries/botbuilder-core/tests/turnContext.test.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,4 +349,27 @@ describe(`TurnContext`, function () {
349349
});
350350
});
351351

352+
it ('should remove at mention from activity', function() {
353+
354+
var activity = {
355+
type: 'message',
356+
text: '<at>TestOAuth619</at> test activity',
357+
recipient: { id: 'TestOAuth619' },
358+
entities: [
359+
{
360+
type: 'mention',
361+
text: `<at>TestOAuth619</at>`,
362+
mentioned: {
363+
name: 'Bot',
364+
id: `TestOAuth619`
365+
}
366+
}
367+
]
368+
};
369+
370+
var text = TurnContext.removeRecipientMention(activity);
371+
372+
assert(text,' test activity');
373+
assert(activity.text,' test activity');
374+
});
352375
});

libraries/botbuilder/src/inspectionMiddleware.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ export class InspectionMiddleware extends InterceptionMiddleware {
188188

189189
if (turnContext.activity.type == ActivityTypes.Message && turnContext.activity.text !== undefined) {
190190

191+
var originalText = turnContext.activity.text;
192+
TurnContext.removeRecipientMention(turnContext.activity);
193+
191194
var command = turnContext.activity.text.trim().split(' ');
192195
if (command.length > 1 && command[0] === InspectionMiddleware.command) {
193196

@@ -201,6 +204,8 @@ export class InspectionMiddleware extends InterceptionMiddleware {
201204
return true;
202205
}
203206
}
207+
208+
turnContext.activity.text = originalText;
204209
}
205210

206211
return false;

libraries/botbuilder/tests/inspectionMiddleware.test.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,100 @@ describe('InspectionMiddleware', function() {
112112

113113
// verify that all our expectations have been met
114114

115+
assert(inboundExpectation.isDone(), 'The expectation of a trace message for the inbound activity was not met');
116+
assert(outboundExpectation.isDone(), 'The expectation of a trace message for the outbound activity was not met');
117+
assert(stateExpectation.isDone(), 'The expectation of a trace message for the bot state was not met');
118+
});
119+
it('should replicate activity data to listening emulator following open and attach with at mention', async function() {
120+
121+
// set up our expectations in nock - each corresponds to a trace message we expect to receive in the emulator
122+
123+
const inboundExpectation = nock('https://test.com')
124+
.post('/v3/conversations/Convo1/activities', activity => activity.type === 'trace'
125+
&& activity.value.text == 'hi')
126+
.reply(200, { id: 'test' });
127+
128+
const outboundExpectation = nock('https://test.com')
129+
.post('/v3/conversations/Convo1/activities', activity => activity.type === 'trace'
130+
&& activity.value.text == 'echo: hi')
131+
.reply(200, { id: 'test' });
132+
133+
const stateExpectation = nock('https://test.com')
134+
.post('/v3/conversations/Convo1/activities', activity => activity.type === 'trace'
135+
&& activity.value.userState && activity.value.userState.x.property == 'hello'
136+
&& activity.value.conversationState && activity.value.conversationState.y.property == 'world')
137+
.reply(200, { id: 'test' });
138+
139+
// create the various storage and middleware objects we will be using
140+
141+
var storage = new MemoryStorage();
142+
var inspectionState = new InspectionState(storage);
143+
var userState = new UserState(storage);
144+
var conversationState = new ConversationState(storage);
145+
var inspectionMiddleware = new InspectionMiddleware(inspectionState, userState, conversationState);
146+
147+
// the emulator sends an /INSPECT open command - we can use another adapter here
148+
149+
var openActivity = MessageFactory.text('/INSPECT open');
150+
151+
const inspectionAdapter = new TestAdapter(async (turnContext) => {
152+
await inspectionMiddleware.processCommand(turnContext);
153+
}, null, true);
154+
155+
await inspectionAdapter.receiveActivity(openActivity);
156+
157+
var inspectionOpenResultActivity = inspectionAdapter.activityBuffer[0];
158+
159+
var recipientId = 'bot';
160+
var attachCommand = `<at>${ recipientId }</at> ${ inspectionOpenResultActivity.value }`;
161+
162+
// the logic of the bot including replying with a message and updating user and conversation state
163+
164+
var x = userState.createProperty('x');
165+
var y = conversationState.createProperty('y');
166+
167+
var applicationAdapter = new TestAdapter(async (turnContext) => {
168+
169+
await turnContext.sendActivity(MessageFactory.text(`echo: ${ turnContext.activity.text }`));
170+
171+
(await x.get(turnContext, { property: '' })).property = 'hello';
172+
(await y.get(turnContext, { property: '' })).property = 'world';
173+
174+
await userState.saveChanges(turnContext);
175+
await conversationState.saveChanges(turnContext);
176+
177+
}, null, true);
178+
179+
// IMPORTANT add the InspectionMiddleware to the adapter that is running our bot
180+
181+
applicationAdapter.use(inspectionMiddleware);
182+
183+
var attachActivity = {
184+
type: 'message',
185+
text: attachCommand,
186+
recipient: { id: recipientId },
187+
entities: [
188+
{
189+
type: 'mention',
190+
text: `<at>${ recipientId }</at>`,
191+
mentioned: {
192+
name: 'Bot',
193+
id: recipientId
194+
}
195+
}
196+
]
197+
};
198+
199+
await applicationAdapter.receiveActivity(attachActivity);
200+
201+
// the attach command response is a informational message
202+
203+
await applicationAdapter.receiveActivity(MessageFactory.text('hi'));
204+
205+
// trace activities should be sent to the emulator using the connector and the conversation reference
206+
207+
// verify that all our expectations have been met
208+
115209
assert(inboundExpectation.isDone(), 'The expectation of a trace message for the inbound activity was not met');
116210
assert(outboundExpectation.isDone(), 'The expectation of a trace message for the outbound activity was not met');
117211
assert(stateExpectation.isDone(), 'The expectation of a trace message for the bot state was not met');

0 commit comments

Comments
 (0)