();
177 |
178 | foreach (string channel in channelArray)
179 | {
180 | channelList.Add(channel.Trim());
181 | }
182 | }
183 | }
184 | else
185 | {
186 | System.Diagnostics.Debug.WriteLine($"No channels defined by key \"{key}\" in app settings");
187 | }
188 |
189 | return channelList;
190 | }
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/IntermediatorBotSample/Pages/Index.cshtml:
--------------------------------------------------------------------------------
1 | @page
2 | @using IntermediatorBotSample.Pages
3 | @model IndexModel
4 | @{
5 | }
6 |
7 | Intermediator Bot Sample
8 |
9 |
10 | The purpose is to serve as a sample on how to implement the human hand-off.
11 | Visit https://github.com/tompaana/intermediator-bot-sample to learn more.
12 |
13 |
14 | The bot endpoint URL is:
15 |
16 |
21 |
22 | The bot credentials are:
23 |
27 |
28 |
34 |
--------------------------------------------------------------------------------
/IntermediatorBotSample/Pages/Index.cshtml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using Microsoft.AspNetCore.Mvc.RazorPages;
3 | using Microsoft.Extensions.Configuration;
4 |
5 | namespace IntermediatorBotSample.Pages
6 | {
7 | public class IndexModel : PageModel
8 | {
9 | [BindProperty]
10 | public string BotEndpointPath
11 | {
12 | get
13 | {
14 | return $"{Configuration["BotBasePath"]}{Configuration["BotMessagesPath"]}";
15 | }
16 | }
17 |
18 | [BindProperty]
19 | public string BotAppId
20 | {
21 | get
22 | {
23 | #if DEBUG
24 | return Configuration["MicrosoftAppId"];
25 | #else
26 | return "None of your business";
27 | #endif
28 | }
29 | }
30 |
31 | [BindProperty]
32 | public string BotAppPassword
33 | {
34 | get
35 | {
36 | #if DEBUG
37 | return Configuration["MicrosoftAppPassword"];
38 | #else
39 | return "Also none of your business";
40 | #endif
41 | }
42 | }
43 |
44 | private IConfiguration Configuration
45 | {
46 | get;
47 | }
48 |
49 | public IndexModel(IConfiguration configuration)
50 | {
51 | Configuration = configuration;
52 | }
53 |
54 | public void OnGet()
55 | {
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/IntermediatorBotSample/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using Microsoft.AspNetCore;
7 | using Microsoft.AspNetCore.Hosting;
8 | using Microsoft.Extensions.Configuration;
9 | using Microsoft.Extensions.Logging;
10 |
11 | namespace IntermediatorBotSample
12 | {
13 | public class Program
14 | {
15 | public static void Main(string[] args)
16 | {
17 | BuildWebHost(args).Run();
18 | }
19 |
20 | public static IWebHost BuildWebHost(string[] args) =>
21 | WebHost.CreateDefaultBuilder(args)
22 | .UseStartup()
23 | .Build();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/IntermediatorBotSample/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:29210/",
7 | "sslPort": 0
8 | }
9 | },
10 | "profiles": {
11 | "IIS Express": {
12 | "commandName": "IISExpress",
13 | "launchBrowser": true,
14 | "environmentVariables": {
15 | "ASPNETCORE_ENVIRONMENT": "Development"
16 | }
17 | },
18 | "IntermediatorBotSample": {
19 | "commandName": "Project",
20 | "launchBrowser": true,
21 | "environmentVariables": {
22 | "ASPNETCORE_ENVIRONMENT": "Development"
23 | },
24 | "applicationUrl": "http://localhost:29211/"
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/IntermediatorBotSample/Resources/StringConstants.cs:
--------------------------------------------------------------------------------
1 | namespace IntermediatorBotSample.Resources
2 | {
3 | public class StringConstants
4 | {
5 | public static readonly string NoUserNamePlaceholder = "(no user name)";
6 | public static readonly string NoNameOrId = "(no name or ID)";
7 |
8 | public static readonly string LineBreak = "\n\r";
9 | public static readonly char QuotationMark = '"';
10 |
11 | // For parsing JSON
12 | public static readonly string EndOfLineInJsonResponse = "\\r\\n";
13 | public static readonly char BackslashInJsonResponse = '\\';
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/IntermediatorBotSample/Resources/Strings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace IntermediatorBotSample.Resources {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Strings {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Strings() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("IntermediatorBotSample.Resources.Strings", typeof(Strings).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized string similar to Accept.
65 | ///
66 | internal static string AcceptButtonTitle {
67 | get {
68 | return ResourceManager.GetString("AcceptButtonTitle", resourceCulture);
69 | }
70 | }
71 |
72 | ///
73 | /// Looks up a localized string similar to Choose the user whose request to accept.
74 | ///
75 | internal static string AcceptConnectionRequestsCardInstructions {
76 | get {
77 | return ResourceManager.GetString("AcceptConnectionRequestsCardInstructions", resourceCulture);
78 | }
79 | }
80 |
81 | ///
82 | /// Looks up a localized string similar to Accept request.
83 | ///
84 | internal static string AcceptConnectionRequestsCardTitle {
85 | get {
86 | return ResourceManager.GetString("AcceptConnectionRequestsCardTitle", resourceCulture);
87 | }
88 | }
89 |
90 | ///
91 | /// Looks up a localized string similar to Accept or reject the request: Type "{0}" to accept, "{1}" to reject or use the buttons if enabled.
92 | ///
93 | internal static string AcceptRejectConnectionHint {
94 | get {
95 | return ResourceManager.GetString("AcceptRejectConnectionHint", resourceCulture);
96 | }
97 | }
98 |
99 | ///
100 | /// Looks up a localized string similar to To set up an aggregation channel, type "{0}".
101 | ///
102 | internal static string AddAggregationChannelCommandHint {
103 | get {
104 | return ResourceManager.GetString("AddAggregationChannelCommandHint", resourceCulture);
105 | }
106 | }
107 |
108 | ///
109 | /// Looks up a localized string similar to This channel/conversation is already receiving requests.
110 | ///
111 | internal static string AggregationChannelAlreadySet {
112 | get {
113 | return ResourceManager.GetString("AggregationChannelAlreadySet", resourceCulture);
114 | }
115 | }
116 |
117 | ///
118 | /// Looks up a localized string similar to This channel/conversation will no longer receive requests.
119 | ///
120 | internal static string AggregationChannelRemoved {
121 | get {
122 | return ResourceManager.GetString("AggregationChannelRemoved", resourceCulture);
123 | }
124 | }
125 |
126 | ///
127 | /// Looks up a localized string similar to This channel/conversation is now where the requests are aggregated.
128 | ///
129 | internal static string AggregationChannelSet {
130 | get {
131 | return ResourceManager.GetString("AggregationChannelSet", resourceCulture);
132 | }
133 | }
134 |
135 | ///
136 | /// Looks up a localized string similar to You are already connected with user "{0}".
137 | ///
138 | internal static string AlreadyConnectedWithUser {
139 | get {
140 | return ResourceManager.GetString("AlreadyConnectedWithUser", resourceCulture);
141 | }
142 | }
143 |
144 | ///
145 | /// Looks up a localized string similar to Apply to all.
146 | ///
147 | internal static string ApplyToAll {
148 | get {
149 | return ResourceManager.GetString("ApplyToAll", resourceCulture);
150 | }
151 | }
152 |
153 | ///
154 | /// Looks up a localized string similar to Bots.
155 | ///
156 | internal static string Bots {
157 | get {
158 | return ResourceManager.GetString("Bots", resourceCulture);
159 | }
160 | }
161 |
162 | ///
163 | /// Looks up a localized string similar to Administrative options for controlling the end user bot conversations.
164 | ///
165 | internal static string CommandMenuDescription {
166 | get {
167 | return ResourceManager.GetString("CommandMenuDescription", resourceCulture);
168 | }
169 | }
170 |
171 | ///
172 | /// Looks up a localized string similar to Select the command using the buttons below or type the command keyword ("{0}")/bot handle ("@{1}") followed by the command and its parameters (if any), for example: "{2}"..
173 | ///
174 | internal static string CommandMenuInstructions {
175 | get {
176 | return ResourceManager.GetString("CommandMenuInstructions", resourceCulture);
177 | }
178 | }
179 |
180 | ///
181 | /// Looks up a localized string similar to Commands.
182 | ///
183 | internal static string CommandMenuTitle {
184 | get {
185 | return ResourceManager.GetString("CommandMenuTitle", resourceCulture);
186 | }
187 | }
188 |
189 | ///
190 | /// Looks up a localized string similar to Command "{0}" not recognized.
191 | ///
192 | internal static string CommandNotRecognized {
193 | get {
194 | return ResourceManager.GetString("CommandNotRecognized", resourceCulture);
195 | }
196 | }
197 |
198 | ///
199 | /// Looks up a localized string similar to A connection request was made, but the requestor party is null.
200 | ///
201 | internal static string ConnectionRequestMadeButRequestorIsNull {
202 | get {
203 | return ResourceManager.GetString("ConnectionRequestMadeButRequestorIsNull", resourceCulture);
204 | }
205 | }
206 |
207 | ///
208 | /// Looks up a localized string similar to Sorry, you are not allowed to accept or reject requests.
209 | ///
210 | internal static string ConnectionRequestResponseNotAllowed {
211 | get {
212 | return ResourceManager.GetString("ConnectionRequestResponseNotAllowed", resourceCulture);
213 | }
214 | }
215 |
216 | ///
217 | /// Looks up a localized string similar to A connection request error occurred: {0}.
218 | ///
219 | internal static string ConnectionRequestResultErrorWithResult {
220 | get {
221 | return ResourceManager.GetString("ConnectionRequestResultErrorWithResult", resourceCulture);
222 | }
223 | }
224 |
225 | ///
226 | /// Looks up a localized string similar to Assistance request.
227 | ///
228 | internal static string ConnectionRequestTitle {
229 | get {
230 | return ResourceManager.GetString("ConnectionRequestTitle", resourceCulture);
231 | }
232 | }
233 |
234 | ///
235 | /// Looks up a localized string similar to A connection error occurred: {0}.
236 | ///
237 | internal static string ConnectionResultErrorWithResult {
238 | get {
239 | return ResourceManager.GetString("ConnectionResultErrorWithResult", resourceCulture);
240 | }
241 | }
242 |
243 | ///
244 | /// Looks up a localized string similar to To initiate a conversation with an agent, type "{0}".
245 | ///
246 | internal static string ConnectRequestCommandHint {
247 | get {
248 | return ResourceManager.GetString("ConnectRequestCommandHint", resourceCulture);
249 | }
250 | }
251 |
252 | ///
253 | /// Looks up a localized string similar to Deleting all data....
254 | ///
255 | internal static string DeletingAllData {
256 | get {
257 | return ResourceManager.GetString("DeletingAllData", resourceCulture);
258 | }
259 | }
260 |
261 | ///
262 | /// Looks up a localized string similar to Deleting all data as requested by "0"....
263 | ///
264 | internal static string DeletingAllDataWithCommandIssuer {
265 | get {
266 | return ResourceManager.GetString("DeletingAllDataWithCommandIssuer", resourceCulture);
267 | }
268 | }
269 |
270 | ///
271 | /// Looks up a localized string similar to You said: {0}.
272 | ///
273 | internal static string EchoMessage {
274 | get {
275 | return ResourceManager.GetString("EchoMessage", resourceCulture);
276 | }
277 | }
278 |
279 | ///
280 | /// Looks up a localized string similar to An error occurred.
281 | ///
282 | internal static string ErrorOccured {
283 | get {
284 | return ResourceManager.GetString("ErrorOccured", resourceCulture);
285 | }
286 | }
287 |
288 | ///
289 | /// Looks up a localized string similar to An error occurred: {0}.
290 | ///
291 | internal static string ErrorOccuredWithResult {
292 | get {
293 | return ResourceManager.GetString("ErrorOccuredWithResult", resourceCulture);
294 | }
295 | }
296 |
297 | ///
298 | /// Looks up a localized string similar to Failed to find the bot instance on aggregation channel "{0}".
299 | ///
300 | internal static string FailedToFindBotOnAggregationChannel {
301 | get {
302 | return ResourceManager.GetString("FailedToFindBotOnAggregationChannel", resourceCulture);
303 | }
304 | }
305 |
306 | ///
307 | /// Looks up a localized string similar to Failed to find the pending request.
308 | ///
309 | internal static string FailedToFindPendingRequest {
310 | get {
311 | return ResourceManager.GetString("FailedToFindPendingRequest", resourceCulture);
312 | }
313 | }
314 |
315 | ///
316 | /// Looks up a localized string similar to Failed to find a pending request for user "0": {1}.
317 | ///
318 | internal static string FailedToFindPendingRequestForUserWithErrorMessage {
319 | get {
320 | return ResourceManager.GetString("FailedToFindPendingRequestForUserWithErrorMessage", resourceCulture);
321 | }
322 | }
323 |
324 | ///
325 | /// Looks up a localized string similar to Failed to forward the message.
326 | ///
327 | internal static string FailedToForwardMessage {
328 | get {
329 | return ResourceManager.GetString("FailedToForwardMessage", resourceCulture);
330 | }
331 | }
332 |
333 | ///
334 | /// Looks up a localized string similar to Failed to reject pending requests.
335 | ///
336 | internal static string FailedToRejectPendingRequests {
337 | get {
338 | return ResourceManager.GetString("FailedToRejectPendingRequests", resourceCulture);
339 | }
340 | }
341 |
342 | ///
343 | /// Looks up a localized string similar to Failed to remove aggregation channel.
344 | ///
345 | internal static string FailedToRemoveAggregationChannel {
346 | get {
347 | return ResourceManager.GetString("FailedToRemoveAggregationChannel", resourceCulture);
348 | }
349 | }
350 |
351 | ///
352 | /// Looks up a localized string similar to Failed to set the aggregation channel: {0}.
353 | ///
354 | internal static string FailedToSetAggregationChannel {
355 | get {
356 | return ResourceManager.GetString("FailedToSetAggregationChannel", resourceCulture);
357 | }
358 | }
359 |
360 | ///
361 | /// Looks up a localized string similar to Invalid command.
362 | ///
363 | internal static string InvalidCommand {
364 | get {
365 | return ResourceManager.GetString("InvalidCommand", resourceCulture);
366 | }
367 | }
368 |
369 | ///
370 | /// Looks up a localized string similar to Command parameter invalid or missing.
371 | ///
372 | internal static string InvalidOrMissingCommandParameter {
373 | get {
374 | return ResourceManager.GetString("InvalidOrMissingCommandParameter", resourceCulture);
375 | }
376 | }
377 |
378 | ///
379 | /// Looks up a localized string similar to Sorry, there are no agents available right now.
380 | ///
381 | internal static string NoAgentsAvailable {
382 | get {
383 | return ResourceManager.GetString("NoAgentsAvailable", resourceCulture);
384 | }
385 | }
386 |
387 | ///
388 | /// Looks up a localized string similar to No aggregation channel set up.
389 | ///
390 | internal static string NoAggregationChannel {
391 | get {
392 | return ResourceManager.GetString("NoAggregationChannel", resourceCulture);
393 | }
394 | }
395 |
396 | ///
397 | /// Looks up a localized string similar to No bots stored.
398 | ///
399 | internal static string NoBotsStored {
400 | get {
401 | return ResourceManager.GetString("NoBotsStored", resourceCulture);
402 | }
403 | }
404 |
405 | ///
406 | /// Looks up a localized string similar to No conversations.
407 | ///
408 | internal static string NoConversations {
409 | get {
410 | return ResourceManager.GetString("NoConversations", resourceCulture);
411 | }
412 | }
413 |
414 | ///
415 | /// Looks up a localized string similar to No pending requests.
416 | ///
417 | internal static string NoPendingRequests {
418 | get {
419 | return ResourceManager.GetString("NoPendingRequests", resourceCulture);
420 | }
421 | }
422 |
423 | ///
424 | /// Looks up a localized string similar to No results.
425 | ///
426 | internal static string NoResults {
427 | get {
428 | return ResourceManager.GetString("NoResults", resourceCulture);
429 | }
430 | }
431 |
432 | ///
433 | /// Looks up a localized string similar to Your request was accepted and you are now chatting with {0}.
434 | ///
435 | internal static string NotifyClientConnected {
436 | get {
437 | return ResourceManager.GetString("NotifyClientConnected", resourceCulture);
438 | }
439 | }
440 |
441 | ///
442 | /// Looks up a localized string similar to Your conversation with {0} has ended.
443 | ///
444 | internal static string NotifyClientDisconnected {
445 | get {
446 | return ResourceManager.GetString("NotifyClientDisconnected", resourceCulture);
447 | }
448 | }
449 |
450 | ///
451 | /// Looks up a localized string similar to Your request has already been received, please wait for an agent to respond.
452 | ///
453 | internal static string NotifyClientDuplicateRequest {
454 | get {
455 | return ResourceManager.GetString("NotifyClientDuplicateRequest", resourceCulture);
456 | }
457 | }
458 |
459 | ///
460 | /// Looks up a localized string similar to Unfortunately your request could not be processed.
461 | ///
462 | internal static string NotifyClientRequestRejected {
463 | get {
464 | return ResourceManager.GetString("NotifyClientRequestRejected", resourceCulture);
465 | }
466 | }
467 |
468 | ///
469 | /// Looks up a localized string similar to Please wait for your request to be accepted.
470 | ///
471 | internal static string NotifyClientWaitForRequestHandling {
472 | get {
473 | return ResourceManager.GetString("NotifyClientWaitForRequestHandling", resourceCulture);
474 | }
475 | }
476 |
477 | ///
478 | /// Looks up a localized string similar to You are now connected to user "{0}".
479 | ///
480 | internal static string NotifyOwnerConnected {
481 | get {
482 | return ResourceManager.GetString("NotifyOwnerConnected", resourceCulture);
483 | }
484 | }
485 |
486 | ///
487 | /// Looks up a localized string similar to You are now disconnected from the conversation with user "{0}".
488 | ///
489 | internal static string NotifyOwnerDisconnected {
490 | get {
491 | return ResourceManager.GetString("NotifyOwnerDisconnected", resourceCulture);
492 | }
493 | }
494 |
495 | ///
496 | /// Looks up a localized string similar to Request from user "{0}" rejected.
497 | ///
498 | internal static string NotifyOwnerRequestRejected {
499 | get {
500 | return ResourceManager.GetString("NotifyOwnerRequestRejected", resourceCulture);
501 | }
502 | }
503 |
504 | ///
505 | /// Looks up a localized string similar to {0} is not a permitted aggregation channel.
506 | ///
507 | internal static string NotPermittedAggregationChannel {
508 | get {
509 | return ResourceManager.GetString("NotPermittedAggregationChannel", resourceCulture);
510 | }
511 | }
512 |
513 | ///
514 | /// Looks up a localized string similar to No users stored.
515 | ///
516 | internal static string NoUsersStored {
517 | get {
518 | return ResourceManager.GetString("NoUsersStored", resourceCulture);
519 | }
520 | }
521 |
522 | ///
523 | /// Looks up a localized string similar to To see the available commands, type "{0}".
524 | ///
525 | internal static string OptionsCommandHint {
526 | get {
527 | return ResourceManager.GetString("OptionsCommandHint", resourceCulture);
528 | }
529 | }
530 |
531 | ///
532 | /// Looks up a localized string similar to Party "{0}" removed.
533 | ///
534 | internal static string PartyRemoved {
535 | get {
536 | return ResourceManager.GetString("PartyRemoved", resourceCulture);
537 | }
538 | }
539 |
540 | ///
541 | /// Looks up a localized string similar to {0} pending request(s) found.
542 | ///
543 | internal static string PendingRequestsFoundWithCount {
544 | get {
545 | return ResourceManager.GetString("PendingRequestsFoundWithCount", resourceCulture);
546 | }
547 | }
548 |
549 | ///
550 | /// Looks up a localized string similar to Reject all.
551 | ///
552 | internal static string RejectAll {
553 | get {
554 | return ResourceManager.GetString("RejectAll", resourceCulture);
555 | }
556 | }
557 |
558 | ///
559 | /// Looks up a localized string similar to Reject.
560 | ///
561 | internal static string RejectButtonTitle {
562 | get {
563 | return ResourceManager.GetString("RejectButtonTitle", resourceCulture);
564 | }
565 | }
566 |
567 | ///
568 | /// Looks up a localized string similar to Reject request.
569 | ///
570 | internal static string RejectConnectionRequestCardTitle {
571 | get {
572 | return ResourceManager.GetString("RejectConnectionRequestCardTitle", resourceCulture);
573 | }
574 | }
575 |
576 | ///
577 | /// Looks up a localized string similar to Choose the user whose request to reject.
578 | ///
579 | internal static string RejectConnectionRequestsCardInstructions {
580 | get {
581 | return ResourceManager.GetString("RejectConnectionRequestsCardInstructions", resourceCulture);
582 | }
583 | }
584 |
585 | ///
586 | /// Looks up a localized string similar to "{0}" on channel "{1}" ({2}).
587 | ///
588 | internal static string RequestorDetailsItem {
589 | get {
590 | return ResourceManager.GetString("RequestorDetailsItem", resourceCulture);
591 | }
592 | }
593 |
594 | ///
595 | /// Looks up a localized string similar to Request was made, but the details of the requestor are missing.
596 | ///
597 | internal static string RequestorDetailsMissing {
598 | get {
599 | return ResourceManager.GetString("RequestorDetailsMissing", resourceCulture);
600 | }
601 | }
602 |
603 | ///
604 | /// Looks up a localized string similar to Requested by user "{0}" on channel "{1}".
605 | ///
606 | internal static string RequestorDetailsTitle {
607 | get {
608 | return ResourceManager.GetString("RequestorDetailsTitle", resourceCulture);
609 | }
610 | }
611 |
612 | ///
613 | /// Looks up a localized string similar to Data of user "{0}" deleted.
614 | ///
615 | internal static string UserDataDeleted {
616 | get {
617 | return ResourceManager.GetString("UserDataDeleted", resourceCulture);
618 | }
619 | }
620 |
621 | ///
622 | /// Looks up a localized string similar to User name missing.
623 | ///
624 | internal static string UserNameMissing {
625 | get {
626 | return ResourceManager.GetString("UserNameMissing", resourceCulture);
627 | }
628 | }
629 |
630 | ///
631 | /// Looks up a localized string similar to Users.
632 | ///
633 | internal static string Users {
634 | get {
635 | return ResourceManager.GetString("Users", resourceCulture);
636 | }
637 | }
638 | }
639 | }
640 |
--------------------------------------------------------------------------------
/IntermediatorBotSample/Resources/Strings.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | Accept
122 |
123 |
124 | Accept or reject the request: Type "{0}" to accept, "{1}" to reject or use the buttons if enabled
125 |
126 |
127 | To set up an aggregation channel, type "{0}"
128 |
129 |
130 | This channel/conversation is already receiving requests
131 |
132 |
133 | This channel/conversation will no longer receive requests
134 |
135 |
136 | This channel/conversation is now where the requests are aggregated
137 |
138 |
139 | You are already connected with user "{0}"
140 |
141 |
142 | Administrative options for controlling the end user bot conversations
143 |
144 |
145 | Select the command using the buttons below or type the command keyword ("{0}")/bot handle ("@{1}") followed by the command and its parameters (if any), for example: "{2}".
146 |
147 |
148 | Commands
149 |
150 |
151 | Command "{0}" not recognized
152 |
153 |
154 | A connection request was made, but the requestor party is null
155 |
156 |
157 | Sorry, you are not allowed to accept or reject requests
158 |
159 |
160 | Assistance request
161 |
162 |
163 | To initiate a conversation with an agent, type "{0}"
164 |
165 |
166 | Deleting all data...
167 |
168 |
169 | Deleting all data as requested by "0"...
170 |
171 |
172 | You said: {0}
173 |
174 |
175 | An error occurred
176 |
177 |
178 | An error occurred: {0}
179 |
180 |
181 | Failed to find the bot instance on aggregation channel "{0}"
182 |
183 |
184 | Failed to find a pending request for user "0": {1}
185 |
186 |
187 | Failed to forward the message
188 |
189 |
190 | Failed to remove aggregation channel
191 |
192 |
193 | Sorry, there are no agents available right now
194 |
195 |
196 | No aggregation channel set up
197 |
198 |
199 | No pending requests
200 |
201 |
202 | Your request was accepted and you are now chatting with {0}
203 |
204 |
205 | Your conversation with {0} has ended
206 |
207 |
208 | Your request has already been received, please wait for an agent to respond
209 |
210 |
211 | Unfortunately your request could not be processed
212 |
213 |
214 | Please wait for your request to be accepted
215 |
216 |
217 | You are now connected to user "{0}"
218 |
219 |
220 | You are now disconnected from the conversation with user "{0}"
221 |
222 |
223 | Request from user "{0}" rejected
224 |
225 |
226 | To see the available commands, type "{0}"
227 |
228 |
229 | Party "{0}" removed
230 |
231 |
232 | Reject
233 |
234 |
235 | Requested by user "{0}" on channel "{1}"
236 |
237 |
238 | Data of user "{0}" deleted
239 |
240 |
241 | User name missing
242 |
243 |
244 | Choose the user whose request to accept
245 |
246 |
247 | Accept request
248 |
249 |
250 | Apply to all
251 |
252 |
253 | Bots
254 |
255 |
256 | Failed to find the pending request
257 |
258 |
259 | Failed to reject pending requests
260 |
261 |
262 | Invalid command
263 |
264 |
265 | Command parameter invalid or missing
266 |
267 |
268 | No bots stored
269 |
270 |
271 | No conversations
272 |
273 |
274 | No results
275 |
276 |
277 | No users stored
278 |
279 |
280 | {0} pending request(s) found
281 |
282 |
283 | Reject all
284 |
285 |
286 | Reject request
287 |
288 |
289 | Choose the user whose request to reject
290 |
291 |
292 | "{0}" on channel "{1}" ({2})
293 |
294 |
295 | Users
296 |
297 |
298 | Request was made, but the details of the requestor are missing
299 |
300 |
301 | A connection request error occurred: {0}
302 |
303 |
304 | A connection error occurred: {0}
305 |
306 |
307 | Failed to set the aggregation channel: {0}
308 |
309 |
310 | {0} is not a permitted aggregation channel
311 |
312 |
--------------------------------------------------------------------------------
/IntermediatorBotSample/Startup.cs:
--------------------------------------------------------------------------------
1 | using IntermediatorBotSample.Bot;
2 | using IntermediatorBotSample.Middleware;
3 | using Microsoft.AspNetCore.Builder;
4 | using Microsoft.AspNetCore.Hosting;
5 | using Microsoft.Bot.Builder;
6 | using Microsoft.Bot.Builder.Core.Extensions;
7 | using Microsoft.Bot.Builder.BotFramework;
8 | using Microsoft.Bot.Builder.Integration;
9 | using Microsoft.Bot.Builder.Integration.AspNet.Core;
10 | using Microsoft.Bot.Builder.TraceExtensions;
11 | using Microsoft.Extensions.Configuration;
12 | using Microsoft.Extensions.DependencyInjection;
13 | using System;
14 |
15 | namespace IntermediatorBotSample
16 | {
17 | public class Startup
18 | {
19 | public IConfiguration Configuration
20 | {
21 | get;
22 | }
23 |
24 | public Startup(IHostingEnvironment env)
25 | {
26 | var builder = new ConfigurationBuilder()
27 | .SetBasePath(env.ContentRootPath)
28 | .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
29 | .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
30 | .AddEnvironmentVariables();
31 |
32 | Configuration = builder.Build();
33 | }
34 |
35 | ///
36 | /// This method gets called by the runtime. Use this method to add services to the container.
37 | ///
38 | ///
39 | public void ConfigureServices(IServiceCollection services)
40 | {
41 | services.AddMvc().AddControllersAsServices();
42 | services.AddSingleton(_ => Configuration);
43 |
44 | services.AddBot(options =>
45 | {
46 | options.CredentialProvider = new ConfigurationCredentialProvider(Configuration);
47 |
48 | // The CatchExceptionMiddleware provides a top-level exception handler for your bot.
49 | // Any exceptions thrown by other Middleware, or by your OnTurn method, will be
50 | // caught here. To facillitate debugging, the exception is sent out, via Trace,
51 | // to the emulator. Trace activities are NOT displayed to users, so in addition
52 | // an "Ooops" message is sent.
53 | options.Middleware.Add(new CatchExceptionMiddleware(async (context, exception) =>
54 | {
55 | await context.TraceActivityAsync("Bot Exception", exception);
56 | await context.SendActivityAsync($"Sorry, it looks like something went wrong: {exception.Message}");
57 | }));
58 |
59 | // The Memory Storage used here is for local bot debugging only. When the bot
60 | // is restarted, anything stored in memory will be gone.
61 | IStorage dataStore = new MemoryStorage();
62 |
63 | // The File data store, shown here, is suitable for bots that run on
64 | // a single machine and need durable state across application restarts.
65 | // IStorage dataStore = new FileStorage(System.IO.Path.GetTempPath());
66 |
67 | // For production bots use the Azure Table Store, Azure Blob, or
68 | // Azure CosmosDB storage provides, as seen below. To include any of
69 | // the Azure based storage providers, add the Microsoft.Bot.Builder.Azure
70 | // Nuget package to your solution. That package is found at:
71 | // https://www.nuget.org/packages/Microsoft.Bot.Builder.Azure/
72 |
73 | // IStorage dataStore = new Microsoft.Bot.Builder.Azure.AzureTableStorage("AzureTablesConnectionString", "TableName");
74 | // IStorage dataStore = new Microsoft.Bot.Builder.Azure.AzureBlobStorage("AzureBlobConnectionString", "containerName");
75 |
76 | // Handoff middleware
77 | options.Middleware.Add(new HandoffMiddleware(Configuration));
78 | });
79 |
80 | services.AddMvc(); // Required Razor pages
81 | }
82 |
83 | ///
84 | /// This method gets called by the runtime.
85 | /// Use this method to configure the HTTP request pipeline.
86 | ///
87 | ///
88 | ///
89 | public void Configure(IApplicationBuilder app, IHostingEnvironment env)
90 | {
91 | if (env.IsDevelopment())
92 | {
93 | app.UseDeveloperExceptionPage();
94 | }
95 |
96 | app.UseDefaultFiles()
97 | .UseStaticFiles()
98 | .UseMvc() // Required Razor pages
99 | .UseBotFramework();
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/IntermediatorBotSample/TeamsManifest/color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tompaana/intermediator-bot-sample/d6448978d2f8991b6c26d3a178a1fca29ea42ad5/IntermediatorBotSample/TeamsManifest/color.png
--------------------------------------------------------------------------------
/IntermediatorBotSample/TeamsManifest/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://statics.teams.microsoft.com/sdk/v1.2/manifest/MicrosoftTeams.schema.json",
3 | "manifestVersion": "1.2",
4 | "version": "0.0.1",
5 | "id": "INSERT YOUR TEAMS APP ID HERE",
6 | "packageName": "com.underscore.intermediator.bot.sample",
7 | "developer": {
8 | "name": "Tomi",
9 | "websiteUrl": "https://github.com/tompaana/intermediator-bot-sample",
10 | "privacyUrl": "https://github.com/tompaana/intermediator-bot-sample/README.md",
11 | "termsOfUseUrl": "https://github.com/tompaana/intermediator-bot-sample/LICENSE"
12 | },
13 | "icons": {
14 | "color": "color.png",
15 | "outline": "outline.png"
16 | },
17 | "name": {
18 | "short": "Intermediator Bot",
19 | "full": "Intermediator Bot Sample"
20 | },
21 | "description": {
22 | "short": "Intermediator Bot Sample",
23 | "full": "A sample bot, that routes messages between two users on different channels."
24 | },
25 | "accentColor": "#00FF00",
26 | "configurableTabs": [
27 | ],
28 | "staticTabs": [
29 | ],
30 | "bots": [
31 | {
32 | "botId": "INSERT YOUR BOT'S MICROSOFT APP ID HERE",
33 | "scopes": [
34 | "personal",
35 | "team"
36 | ]
37 | }
38 | ],
39 | "permissions": [
40 | "identity",
41 | "messageTeamMembers"
42 | ],
43 | "validDomains": [
44 | "*.azurewebsites.net"
45 | ]
46 | }
--------------------------------------------------------------------------------
/IntermediatorBotSample/TeamsManifest/outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tompaana/intermediator-bot-sample/d6448978d2f8991b6c26d3a178a1fca29ea42ad5/IntermediatorBotSample/TeamsManifest/outline.png
--------------------------------------------------------------------------------
/IntermediatorBotSample/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "MicrosoftAppId": "",
3 | "MicrosoftAppPassword": "",
4 | "BotBasePath": "/api",
5 | "BotMessagesPath": "/messages",
6 | "AzureTableStorageConnectionString": "",
7 | "RejectConnectionRequestIfNoAggregationChannel": true,
8 | "PermittedAggregationChannels": "",
9 | "NoDirectConversationsWithChannels": "emulator, facebook, skype, msteams, webchat"
10 | }
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016-2019 Microsoft
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Intermediator Bot Sample #
2 |
3 | [](https://ci.appveyor.com/project/tompaana/intermediator-bot-sample)
4 |
5 | This is a sample bot, built with the [Microsoft Bot Framework](https://dev.botframework.com/) (v4),
6 | that routes messages between two users on different channels. This sample utilizes the
7 | [Bot Message Routing (component) project](https://github.com/tompaana/bot-message-routing).
8 | The general gist of the message routing is explained in this article:
9 | [Chatbots as Middlemen blog post](https://tompaana.github.io/content/chatbots_as_middlemen.html).
10 |
11 | A possible use case for this type of a bot would be a customer service scenario where the bot relays
12 | the messages between a customer and a customer service agent.
13 |
14 | This is a C# sample targeting the latest version (v4) of the Microsoft Bot Framework. The sample
15 | did previously target the v3.x and you can find that last release
16 | [here](https://github.com/tompaana/intermediator-bot-sample/releases/tag/v1.1).
17 |
18 | If you prefer **Node.js**, fear not, there are these two great samples to look into:
19 |
20 | * [botframework-v4-handoff](https://github.com/GeekTrainer/botframework-v4-handoff)
21 | * [Bot-HandOff (v3)](https://github.com/palindromed/Bot-HandOff)
22 |
23 | #### Contents ####
24 |
25 | * [Getting started](#getting-started)
26 | * [Deploying the bot](#deploying-the-bot)
27 | * [App settings and credentials](#app-settings-and-credentials)
28 | * [Testing the hand-off](#testing-the-hand-off)
29 | * [About the implementation](#implementation)
30 | * [Custom agent portal](#what-if-i-want-to-have-a-custom-agent-portalchannel)
31 | * [Helpful links](#see-also)
32 |
33 | ## Getting started ##
34 |
35 | Since this is an advanced bot scenario, the prerequisites include that you are familiar with the
36 | basic concepts of the Microsoft Bot Framework and you know the C# programming language. Before
37 | getting started it is recommended that you have the following tools installed:
38 |
39 | * [Visual Studio IDE](https://www.visualstudio.com/vs/)
40 | * [ngrok](https://ngrok.com/)
41 | * [Bot Framework Emulator](https://github.com/Microsoft/BotFramework-Emulator) ([download](https://github.com/Microsoft/BotFramework-Emulator/releases))
42 | * [Debug bots with the Bot Framework Emulator](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-debug-emulator?view=azure-bot-service-4.0)
43 |
44 | Altough the bot can be practically hosted anywhere, the deployment instructions (below) are for
45 | Azure. If you don't have an Azure subscription yet, you can get one for free here:
46 | [Create your Azure free account today](https://azure.microsoft.com/en-us/free/).
47 |
48 | ## Deploying the bot ##
49 |
50 | This sample demonstrates routing messages between different users on different channels. Hence,
51 | using only the emulator to test the sample may prove difficult. To utilize other channels, you must
52 | first compile and publish the bot:
53 |
54 | 1. Open the solution (`IntermediatorBotSample.sln`) in Visual Studio/your IDE and make sure it
55 | compiles without any errors (or warnings)
56 | 2. Follow the steps in this article carefully:
57 | [Deploy your bot to Azure](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-deploy-azure?view=azure-bot-service-4.0)
58 | * Top tip: Create a new [Azure resource group](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-overview#resource-groups)
59 | for the app so that if stuff goes wrong, it's really easy to just delete the whole group and
60 | start over
61 | * Having issues testing the bot (as in "The dang thing doesn't work!!") - check the following:
62 | * Did you remember to include `/api/messages` in the messaging endpoint
63 | (Bot Channels Registration/Settings)?
64 | * Did you remember to create and add the credentials (`MicrosoftAppId` and `MicrosoftAppPassword`)?
65 | 3. Add the credentials (`MicrosoftAppId` and `MicrosoftAppPassword`) to the
66 | [`appsettings.json` file](/IntermediatorBotSample/appsettings.json) and republish the bot - now
67 | all you need to do to republish is to right-click the app project in the **Solution Explorer** in
68 | Visual Studio, select **Publish...** and click the **Publish** button on the tab (named in the
69 | sample "IntermediatorBotSample").
70 |
71 | ## App settings and credentials ##
72 |
73 | App settings and credentials are available in the
74 | [`appsettings.json`](/IntermediatorBotSample/appsettings.json)
75 | file of this sample. The file contains both bot and storage credentials as well as the settings that
76 | can be used to tailor the experience. The default content of the file looks something like this:
77 |
78 | ```json
79 | {
80 | "MicrosoftAppId": "",
81 | "MicrosoftAppPassword": "",
82 | "BotBasePath": "/api",
83 | "BotMessagesPath": "/messages",
84 | "AzureTableStorageConnectionString": "",
85 | "RejectConnectionRequestIfNoAggregationChannel": true,
86 | "PermittedAggregationChannels": "",
87 | "NoDirectConversationsWithChannels": "emulator, facebook, skype, msteams, webchat"
88 | }
89 | ```
90 |
91 | ### Settings ###
92 |
93 | * `BotBasePath` and `BotMessagesPath` can be used to define the messaging endpoint of the bot. The
94 | endpoint by default is `http(s):///api/messages`.
95 | * `RejectConnectionRequestIfNoAggregationChannel` defines whether to reject connection requests
96 | automatically if no aggregation channel is set or not. Pretty straightforward, don't you think?
97 | * `PermittedAggregationChannels` can be used to rule out certain channels as aggregation channels.
98 | If, for instance, the list contains `facebook`, the bot will refuse to set any Facebook
99 | conversation as an aggregation channel.
100 | * The solution allows the bot to try and create direct conversations with the accepted "customers".
101 | `NoDirectConversationsWithChannels` defines the channels where the bot should not try to do this.
102 |
103 | ### Credentials ###
104 |
105 | * `MicrosoftAppId` and `MicrosoftAppPassword` should contain the bot's credentials, which you
106 | acquire from the [Azure portal](https://portal.azure.com) when you publish the bot.
107 | * The bot needs to have a centralized storage for routing data. When you insert a valid Azure Table
108 | Storage connection string as the value of the `AzureTableStorageConnectionString` property, the
109 | storage automatically taken in use.
110 |
111 | ## Testing the hand-off ##
112 |
113 | This scenario utilizes an aggregation concept (see the terminology table in this document). One or
114 | more channels act as aggregated channels where the customer requests (for human assistance) are
115 | sent. The conversation owners (e.g. customer service agents) then accept or reject the requests.
116 |
117 | Once you have published the bot, go to the channel you want to receive the requests and issue the
118 | following command to the bot (given that you haven't changed the default bot command handler or the
119 | command itself):
120 |
121 | ```
122 | @ watch
123 | ```
124 |
125 | In case mentions are not supported, you can also use the command keyword:
126 |
127 | ```
128 | command watch
129 | ```
130 |
131 | Now all the requests from another channels are forwarded to this channel.
132 | See the default flow below:
133 |
134 | | Teams | Slack |
135 | | ----- | ----- |
136 | |  | |
137 | | |  | |
138 | |  | |
139 | |  |  |
140 |
141 | ### Commands ###
142 |
143 | The bot comes with a simple command handling mechanism, which supports the commands in the table
144 | below.
145 |
146 | | Command | Description |
147 | | ------- | ----------- |
148 | | `showOptions` | Displays the command options as a card with buttons (convenient!) |
149 | | `Watch` | Marks the current channel as **aggregation** channel (where requests are sent). |
150 | | `Unwatch` | Removes the current channel from the list of aggregation channels. |
151 | | `GetRequests` | Lists all pending connection requests. |
152 | | `AcceptRequest ` | Accepts the conversation connection request of the given user. If no user ID is entered, the bot will render a nice card with accept/reject buttons given that pending connection requests exist. |
153 | | `RejectRequest ` | Rejects the conversation connection request of the given user. If no user ID is entered, the bot will render a nice card with accept/reject buttons given that pending connection requests exist. |
154 | | `Disconnect` | Ends the current conversation with a user. |
155 |
156 | To issue a command use the bot name:
157 |
158 | ```
159 | @
160 | ```
161 |
162 | In case mentions are not supported, you can also use the command keyword:
163 |
164 | ```
165 | command
166 | ```
167 |
168 | Although not an actual command, typing `human` will initiate a connection request, which an agent
169 | can then reject or accept.
170 |
171 | ## Implementation ##
172 |
173 | The core message routing functionality comes from the
174 | [Bot Message Routing (component)](https://github.com/tompaana/bot-message-routing) project.
175 | This sample demonstrates how to use the component and provides the necessary "plumbing" such as
176 | command handling. Here are the main classes of the sample:
177 |
178 | * **[HandoffMiddleware](/IntermediatorBotSample/Middleware/HandoffMiddleware.cs)**: Contains all the
179 | components (class instances) required by the hand-off and implements the main logic flow. This
180 | middleware class will check every incoming message for hand-off related actions.
181 | * **[CommandHandler](/IntermediatorBotSample/CommandHandling/CommandHandler.cs)**:
182 | Provides implementation for checking and acting on commands in messages before they are passed to
183 | a dialog etc.
184 | * **[MessageRouterResultHandler](/IntermediatorBotSample/MessageRouting/MessageRouterResultHandler.cs)**:
185 | Handles the results of the operations executed by the **`MessageRouter`** of the Bot Message
186 | Routing component.
187 | * **[ConnectionRequestHandler](/IntermediatorBotSample/MessageRouting/ConnectionRequestHandler.cs)**:
188 | Implements the main logic for accepting or rejecting connection requests.
189 |
190 | ## What if I want to have a custom agent portal/channel? ##
191 |
192 | Well, right now you have to implement it. There are couple of different ways to go about it. It's
193 | hard to say which one is the best, but if I were to do it, I'd propably start by...
194 |
195 | 1. ...implementing a REST API endpoint
196 | (see for instance [Create a Web API with ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-2.1)).
197 | Then I'd hook the REST API into the message routing code and finally removed the text based
198 | command handling altogether.
199 | 2. Another options is to use back channel messages and hook them up into the current command
200 | pipeline. Granted some changes would need to be made to separate the direct commands from the
201 | back channel ones. Also, the response would likely need to be (or recommended to be) in JSON.
202 | 3. Something else - remember, it's just a web app!
203 |
204 | ## See also ##
205 |
206 | * [Bot Message Routing (component) project](https://github.com/tompaana/bot-message-routing)
207 | * [NuGet package](https://www.nuget.org/packages/BotMessageRouting)
208 | * [Chatbots as Middlemen blog post](https://tompaana.github.io/content/chatbots_as_middlemen.html)
209 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: 1.9.{build}
2 | image: Visual Studio 2017
3 | configuration: Release
4 | platform: Any CPU
5 | before_build:
6 | - cmd: dotnet --version
7 | - cmd: nuget restore
8 | build:
9 | verbosity: minimal
10 | test:
11 | assemblies:
12 | only:
13 | - '**\*.Tests.dll'
14 |
--------------------------------------------------------------------------------