-
Notifications
You must be signed in to change notification settings - Fork 177
/
Copy pathRingCentralAdapter.cs
182 lines (167 loc) · 9.71 KB
/
RingCentralAdapter.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
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Bot.Builder.Community.Adapters.RingCentral.Handoff;
using Microsoft.AspNetCore.Http;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using static Bot.Builder.Community.Adapters.RingCentral.RingCentralConstants;
namespace Bot.Builder.Community.Adapters.RingCentral
{
/// <summary>
/// A Bot Builder adapter implementation used to handle RingCentral webhook HTTP requests.
/// </summary>
public class RingCentralAdapter : BotAdapter, IBotFrameworkHttpAdapter
{
private readonly RingCentralClientWrapper _ringCentralClient;
private readonly ILogger _logger;
private readonly IBotFrameworkHttpAdapter _botAdapter;
private readonly IHandoffRequestRecognizer _handoffRequestRecognizer;
/// <summary>
/// Initializes a new instance of the <see cref="RingCentralAdapter"/> class.
/// </summary>
/// <param name="ringCentralClient">RingCentralClient instance.</param>
/// <param name="botAdapter">BotAdapter of the bot.</param>
/// <param name="handoffRequestRecognizer">Recognizer to determine if it's a human or bot request.</param>
/// <param name="logger">Logger instance.</param>
public RingCentralAdapter(
RingCentralClientWrapper ringCentralClient,
IBotFrameworkHttpAdapter botAdapter,
IHandoffRequestRecognizer handoffRequestRecognizer,
ILogger logger = null)
{
_ringCentralClient = ringCentralClient ?? throw new ArgumentNullException(nameof(ringCentralClient));
_botAdapter = botAdapter ?? throw new ArgumentNullException(nameof(botAdapter));
_handoffRequestRecognizer = handoffRequestRecognizer ?? throw new ArgumentNullException(nameof(handoffRequestRecognizer));
_logger = logger ?? NullLogger.Instance;
}
/// <summary>
/// Sends activities generated by the bot to RingCentral.
/// </summary>
/// <param name="turnContext">The context object for this turn.</param>
/// <param name="activities">Activities to be sent to the RingCentral content API.</param>
/// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
/// <returns>Responses that contains resource id's.</returns>
/// <exception cref="ArgumentNullException"><paramref name="activities"/> is null.</exception>
public override async Task<ResourceResponse[]> SendActivitiesAsync(ITurnContext turnContext, Activity[] activities, CancellationToken cancellationToken)
{
activities = activities ?? throw new ArgumentNullException(nameof(activities));
var responses = new List<ResourceResponse>();
foreach (var activity in activities)
{
if (activity.Type == ActivityTypes.Message)
{
var hasChannelData = turnContext.Activity.TryGetChannelData<RingCentralChannelData>(out var channelData);
if (hasChannelData)
{
var resourceId = await _ringCentralClient.SendContentToRingCentralAsync(activity, channelData.SourceId).ConfigureAwait(false);
responses.Add(new ResourceResponse() { Id = resourceId });
}
else
{
_logger.LogTrace($"SendActivitiesAsync: Required channel data of type {typeof(RingCentralChannelData)} is not present on message.");
}
}
else
{
_logger.LogTrace(
"SendActivitiesAsync: Did not send activity with id '{ActivityId}' to RingCentral. Activity type '{NotSupportedActivityType}' is not supported. Only activities of type '{SupportedActivityType}' are supported.",
activity.Id,
activity.Type,
ActivityTypes.Message);
}
}
return responses.ToArray();
}
/// <summary>
/// This method can be called from inside a POST method on any controller implementation.
/// It handles RingCentral webhooks of different types.
/// </summary>
/// <param name="httpRequest">The HTTP request object, typically in a POST handler by a controller.</param>
/// <param name="httpResponse">When this method completes, the HTTP response to send.</param>
/// <param name="bot">The bot that will handle the incoming activity.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <returns>A task that represents the work queued to execute.</returns>
/// <exception cref="ArgumentNullException">Either <paramref name="httpRequest"/>, <paramref name="httpResponse"/>, <paramref name="bot"/> is null.</exception>
public async Task ProcessAsync(
HttpRequest httpRequest,
HttpResponse httpResponse,
IBot bot,
CancellationToken cancellationToken = default)
{
_ = httpRequest ?? throw new ArgumentNullException(nameof(httpRequest));
_ = httpResponse ?? throw new ArgumentNullException(nameof(httpResponse));
_ = bot ?? throw new ArgumentNullException(nameof(bot));
var (ringCentralRequestType, activity) = new Tuple<RingCentralHandledEvent, Activity>(RingCentralHandledEvent.VerifyWebhook, null);
// CONSIDER: DetectRequestType method
// CONSDIER: Should we prevent giving request and response out of hands here? (hard to know who and when this objects might change)
if (httpRequest.Query != null && httpRequest.Query.ContainsKey("hub.mode"))
{
ringCentralRequestType = RingCentralHandledEvent.VerifyWebhook;
}
else
{
(ringCentralRequestType, activity) = await _ringCentralClient.GetActivityFromRingCentralRequestAsync(this, _botAdapter, httpRequest, httpResponse).ConfigureAwait(false);
}
switch (ringCentralRequestType)
{
case RingCentralHandledEvent.VerifyWebhook:
await _ringCentralClient.VerifyWebhookAsync(httpRequest, httpResponse, cancellationToken).ConfigureAwait(false);
break;
case RingCentralHandledEvent.Intervention:
case RingCentralHandledEvent.Action:
{
// Process human agent responses coming from RingCentral (Custom Source SDK)
if (activity != null)
{
using var context = new TurnContext(this, activity);
await RunPipelineAsync(context, bot.OnTurnAsync, cancellationToken).ConfigureAwait(false);
}
break;
}
case RingCentralHandledEvent.ContentImported:
{
// Process messages created by any subscribed RingCentral source configured using a Webhook
if (activity != null)
{
var handoffRequestStatus = await _handoffRequestRecognizer.RecognizeHandoffRequestAsync(activity).ConfigureAwait(false);
// Bot or Agent request -> Re-Categorize
if (handoffRequestStatus != HandoffTarget.None)
{
var channelData = activity.GetChannelData<RingCentralChannelData>();
var threadId = channelData.ThreadId;
var thread = await _ringCentralClient.GetThreadByIdAsync(threadId).ConfigureAwait(false);
if (thread != null)
{
await _ringCentralClient.HandoffConversationControlToAsync(handoffRequestStatus, thread).ConfigureAwait(false);
}
else
{
_logger.LogWarning("Could not handoff the conversation, thread with thread id \"{ThreadId}\" could not be found.", threadId);
}
}
// Bot or no specific request
if (handoffRequestStatus != HandoffTarget.Agent)
{
using var context = new TurnContext(this, activity);
await RunPipelineAsync(context, bot.OnTurnAsync, cancellationToken).ConfigureAwait(false);
}
}
break;
}
case RingCentralHandledEvent.Unknown:
default:
_logger.LogWarning($"Unsupported RingCentral Webhook or payload: '{httpRequest.PathBase}'.");
break;
}
}
public override Task<ResourceResponse> UpdateActivityAsync(ITurnContext turnContext, Activity activity, CancellationToken cancellationToken)
=> throw new NotImplementedException();
public override Task DeleteActivityAsync(ITurnContext turnContext, ConversationReference reference, CancellationToken cancellationToken)
=> throw new NotImplementedException();
}
}