-
Notifications
You must be signed in to change notification settings - Fork 66
/
Copy pathCommandHandler.cs
330 lines (290 loc) · 14.5 KB
/
CommandHandler.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
using IntermediatorBotSample.MessageRouting;
using IntermediatorBotSample.Resources;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Connector;
using Microsoft.Bot.Schema;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Underscore.Bot.MessageRouting;
using Underscore.Bot.MessageRouting.DataStore;
using Underscore.Bot.MessageRouting.Models;
using Underscore.Bot.MessageRouting.Results;
namespace IntermediatorBotSample.CommandHandling
{
/// <summary>
/// Handler for bot commands related to message routing.
/// </summary>
public class CommandHandler
{
private MessageRouter _messageRouter;
private MessageRouterResultHandler _messageRouterResultHandler;
private ConnectionRequestHandler _connectionRequestHandler;
private IList<string> _permittedAggregationChannels;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="messageRouter">The message router.</param>
/// <param name="messageRouterResultHandler">A MessageRouterResultHandler instance for
/// handling possible routing actions such as accepting connection requests.</param>
/// <param name="connectionRequestHandler">The connection request handler.</param>
/// <param name="permittedAggregationChannels">Permitted aggregation channels.
/// Null list means all channels are allowed.</param>
public CommandHandler(
MessageRouter messageRouter,
MessageRouterResultHandler messageRouterResultHandler,
ConnectionRequestHandler connectionRequestHandler,
IList<string> permittedAggregationChannels = null)
{
_messageRouter = messageRouter;
_messageRouterResultHandler = messageRouterResultHandler;
_connectionRequestHandler = connectionRequestHandler;
_permittedAggregationChannels = permittedAggregationChannels;
}
/// <summary>
/// Checks the given activity for a possible command.
/// </summary>
/// <param name="activity">The context containing the activity, which in turn may contain a possible command.</param>
/// <returns>True, if a command was detected and handled. False otherwise.</returns>
public async virtual Task<bool> HandleCommandAsync(ITurnContext context)
{
Activity activity = context.Activity;
Command command = Command.FromMessageActivity(activity);
if (command == null)
{
// Check for back channel command
command = Command.FromChannelData(activity);
}
if (command == null)
{
return false;
}
bool wasHandled = false;
Activity replyActivity = null;
ConversationReference sender = MessageRouter.CreateSenderConversationReference(activity);
switch (command.BaseCommand)
{
case Commands.ShowOptions:
// Present all command options in a card
replyActivity = CommandCardFactory.AddCardToActivity(
activity.CreateReply(), CommandCardFactory.CreateCommandOptionsCard(activity.Recipient?.Name));
wasHandled = true;
break;
case Commands.Watch:
// Add the sender's channel/conversation into the list of aggregation channels
bool isPermittedAggregationChannel = false;
if (_permittedAggregationChannels != null && _permittedAggregationChannels.Count > 0)
{
foreach (string permittedAggregationChannel in _permittedAggregationChannels)
{
if (!string.IsNullOrWhiteSpace(activity.ChannelId)
&& activity.ChannelId.ToLower().Equals(permittedAggregationChannel.ToLower()))
{
isPermittedAggregationChannel = true;
break;
}
}
}
else
{
isPermittedAggregationChannel = true;
}
if (isPermittedAggregationChannel)
{
ConversationReference aggregationChannelToAdd = new ConversationReference(
null, null, null,
activity.Conversation, activity.ChannelId, activity.ServiceUrl);
ModifyRoutingDataResult modifyRoutingDataResult =
_messageRouter.RoutingDataManager.AddAggregationChannel(aggregationChannelToAdd);
if (modifyRoutingDataResult.Type == ModifyRoutingDataResultType.Added)
{
replyActivity = activity.CreateReply(Strings.AggregationChannelSet);
}
else if (modifyRoutingDataResult.Type == ModifyRoutingDataResultType.AlreadyExists)
{
replyActivity = activity.CreateReply(Strings.AggregationChannelAlreadySet);
}
else if (modifyRoutingDataResult.Type == ModifyRoutingDataResultType.Error)
{
replyActivity = activity.CreateReply(
string.Format(Strings.FailedToSetAggregationChannel, modifyRoutingDataResult.ErrorMessage));
}
}
else
{
replyActivity = activity.CreateReply(
string.Format(Strings.NotPermittedAggregationChannel, activity.ChannelId));
}
wasHandled = true;
break;
case Commands.Unwatch:
// Remove the sender's channel/conversation from the list of aggregation channels
if (_messageRouter.RoutingDataManager.IsAssociatedWithAggregation(sender))
{
ConversationReference aggregationChannelToRemove = new ConversationReference(
null, null, null,
activity.Conversation, activity.ChannelId, activity.ServiceUrl);
if (_messageRouter.RoutingDataManager.RemoveAggregationChannel(aggregationChannelToRemove))
{
replyActivity = activity.CreateReply(Strings.AggregationChannelRemoved);
}
else
{
replyActivity = activity.CreateReply(Strings.FailedToRemoveAggregationChannel);
}
wasHandled = true;
}
break;
case Commands.GetRequests:
IList<ConnectionRequest> connectionRequests =
_messageRouter.RoutingDataManager.GetConnectionRequests();
replyActivity = activity.CreateReply();
if (connectionRequests.Count == 0)
{
replyActivity.Text = Strings.NoPendingRequests;
}
else
{
replyActivity.Attachments = CommandCardFactory.CreateMultipleConnectionRequestCards(
connectionRequests, activity.Recipient?.Name);
}
replyActivity.ChannelData = JsonConvert.SerializeObject(connectionRequests);
wasHandled = true;
break;
case Commands.AcceptRequest:
case Commands.RejectRequest:
// Accept/reject connection request
bool doAccept = (command.BaseCommand == Commands.AcceptRequest);
if (_messageRouter.RoutingDataManager.IsAssociatedWithAggregation(sender))
{
// The sender is associated with the aggregation and has the right to accept/reject
if (command.Parameters.Count == 0)
{
replyActivity = activity.CreateReply();
connectionRequests =
_messageRouter.RoutingDataManager.GetConnectionRequests();
if (connectionRequests.Count == 0)
{
replyActivity.Text = Strings.NoPendingRequests;
}
else
{
replyActivity = CommandCardFactory.AddCardToActivity(
replyActivity, CommandCardFactory.CreateMultiConnectionRequestCard(
connectionRequests, doAccept, activity.Recipient?.Name));
}
}
else if (!doAccept
&& command.Parameters[0].Equals(Command.CommandParameterAll))
{
// Reject all pending connection requests
if (!await _connectionRequestHandler.RejectAllPendingRequestsAsync(
_messageRouter, _messageRouterResultHandler))
{
replyActivity = activity.CreateReply();
replyActivity.Text = Strings.FailedToRejectPendingRequests;
}
}
else if (command.Parameters.Count > 1)
{
// Try to accept/reject the specified connection request
ChannelAccount requestorChannelAccount =
new ChannelAccount(command.Parameters[0]);
ConversationAccount requestorConversationAccount =
new ConversationAccount(null, null, command.Parameters[1]);
AbstractMessageRouterResult messageRouterResult =
await _connectionRequestHandler.AcceptOrRejectRequestAsync(
_messageRouter, _messageRouterResultHandler, sender, doAccept,
requestorChannelAccount, requestorConversationAccount);
await _messageRouterResultHandler.HandleResultAsync(messageRouterResult);
}
else
{
replyActivity = activity.CreateReply(Strings.InvalidOrMissingCommandParameter);
}
}
#if DEBUG
// We shouldn't respond to command attempts by regular users, but I guess
// it's okay when debugging
else
{
replyActivity = activity.CreateReply(Strings.ConnectionRequestResponseNotAllowed);
}
#endif
wasHandled = true;
break;
case Commands.Disconnect:
// End the 1:1 conversation(s)
IList<ConnectionResult> disconnectResults = _messageRouter.Disconnect(sender);
if (disconnectResults != null && disconnectResults.Count > 0)
{
foreach (ConnectionResult disconnectResult in disconnectResults)
{
await _messageRouterResultHandler.HandleResultAsync(disconnectResult);
}
wasHandled = true;
}
break;
default:
replyActivity = activity.CreateReply(string.Format(Strings.CommandNotRecognized, command.BaseCommand));
break;
}
if (replyActivity != null)
{
await context.SendActivityAsync(replyActivity);
}
return wasHandled;
}
/// <summary>
/// Checks the given activity and determines whether the message was addressed directly to
/// the bot or not.
///
/// Note: Only mentions are inspected at the moment.
/// </summary>
/// <param name="messageActivity">The message activity.</param>
/// <param name="strict">Use false for channels that do not properly support mentions.</param>
/// <returns>True, if the message was address directly to the bot. False otherwise.</returns>
public bool WasBotAddressedDirectly(IMessageActivity messageActivity, bool strict = true)
{
bool botWasMentioned = false;
if (strict)
{
Mention[] mentions = messageActivity.GetMentions();
foreach (Mention mention in mentions)
{
foreach (ConversationReference bot in _messageRouter.RoutingDataManager.GetBotInstances())
{
if (mention.Mentioned.Id.Equals(RoutingDataManager.GetChannelAccount(bot).Id))
{
botWasMentioned = true;
break;
}
}
}
}
else
{
// Here we assume the message starts with the bot name, for instance:
//
// * "@<BOT NAME>..."
// * "<BOT NAME>: ..."
string botName = messageActivity.Recipient?.Name;
string message = messageActivity.Text?.Trim();
if (!string.IsNullOrEmpty(botName) && !string.IsNullOrEmpty(message) && message.Length > botName.Length)
{
try
{
message = message.Remove(botName.Length + 1, message.Length - botName.Length - 1);
botWasMentioned = message.Contains(botName);
}
catch (ArgumentOutOfRangeException e)
{
System.Diagnostics.Debug.WriteLine($"Failed to check if bot was mentioned: {e.Message}");
}
}
}
return botWasMentioned;
}
}
}