示例#1
0
        /// <summary>
        /// Process incoming activity. This could be a message coming in from a channel (e.g. WebChat) or an
        /// agent reply coming RingCentral.
        /// </summary>
        /// <param name="turnContext"> The context object for this turn.</param>
        /// <param name="next">The delegate to call to continue the bot middleware pipeline.</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>
        /// <remarks>
        /// Middleware calls the next delegate to pass control to the next middleware in
        /// the pipeline. If middleware doesn’t call the next delegate, the adapter does
        /// not call any of the subsequent middleware’s request handlers or the bot’s receive
        /// handler, and the pipeline short circuits.
        /// The <paramref name="turnContext"/> provides information about the incoming activity, and other data
        /// needed to process the activity.
        /// </remarks>
        public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default)
        {
            bool isFromRingCentral = MessageFromRingCentralOperator(turnContext.Activity);

            if (turnContext.Activity.Type == ActivityTypes.Message && !isFromRingCentral)
            {
                await _ringCentralClient.SendActivityToRingCentralAsync(turnContext.Activity).ConfigureAwait(false);

                var handoffRequestState = await _handoffRequestRecognizer.RecognizeHandoffRequestAsync(turnContext.Activity).ConfigureAwait(false);

                if (handoffRequestState != HandoffTarget.None)
                {
                    string foreignThreadId = RingCentralSdkHelper.BuildForeignThreadIdFromActivity(turnContext.Activity);
                    var    thread          = await _ringCentralClient.GetThreadByForeignThreadIdAsync(foreignThreadId).ConfigureAwait(false);

                    if (thread != null)
                    {
                        await _ringCentralClient.HandoffConversationControlToAsync(handoffRequestState, thread).ConfigureAwait(false);

                        await turnContext.SendActivityAsync($"Transfer to {handoffRequestState.ToString()} has been initiated.").ConfigureAwait(false);

                        if (handoffRequestState == HandoffTarget.Bot)
                        {
                            return;
                        }
                    }
                    else
                    {
                        _logger.LogWarning("Could not handoff the conversation, thread with foreign id \"{ForeignThreadId}\"could not be found.", foreignThreadId);
                    }
                }
            }

            // Hook on messages that are sent from the bot to the user
            turnContext.OnSendActivities(SendActivitiesToUserHook);

            await next(cancellationToken).ConfigureAwait(false);
        }
        /// <summary>
        /// Resolves the IActivity from the RingCentral payload, will send appropriate response back to RingCentral and return the type of RingCentral event to handle
        /// A single webhook endpoint can handle multiple events from RingCentral - therefore need to resolve what type of event this is from the payload
        /// Messages can also be posted to the adapter from a RingCentral Custom Source - which can be configured to be the bot endpoint.  This is used in the scenario
        /// of human handoff when a Intervention.Opened is when a human operator takes over a conversation (eg. "Engages").
        /// </summary>
        /// <param name="adapter">RingCentral adapter.</param>
        /// <param name="botAdapter">Bot adapter.</param>
        /// <param name="request">HttpRequest from caller.</param>
        /// <param name="response">HttpResponse for caller.</param>
        /// <returns>Task.</returns>
        public async Task <Tuple <RingCentralHandledEvent, Activity> > GetActivityFromRingCentralRequestAsync(RingCentralAdapter adapter, IBotFrameworkHttpAdapter botAdapter, HttpRequest request, HttpResponse response)
        {
            _ = adapter ?? throw new ArgumentNullException(nameof(adapter));
            _ = botAdapter ?? throw new ArgumentNullException(nameof(botAdapter));
            _ = request ?? throw new ArgumentNullException(nameof(request));
            _ = response ?? throw new ArgumentNullException(nameof(response));

            var payloadType = await GetTypedRingCentralPayloadAsync(request.Body);

            switch (payloadType)
            {
            case RingCentralEngageEvent ringCentralEngageEvent:
            {
                var metadata = ringCentralEngageEvent.Events.FirstOrDefault()?.Resource?.Metadata;
                if (ringCentralEngageEvent.Events.FirstOrDefault().Type.Equals(RingCentralEventDescription.ContentImported, StringComparison.InvariantCultureIgnoreCase))
                {
                    var newMessageActivity = await GetActivityFromRingCentralEventAsync(ringCentralEngageEvent, response);

                    if (newMessageActivity == null)
                    {
                        break;
                    }

                    var handoffRequestStatus = await _handoffRequestRecognizer.RecognizeHandoffRequestAsync(newMessageActivity);

                    // Bot requsted or bot in charge (not agent), return an activity
                    if (handoffRequestStatus == HandoffTarget.Bot ||
                        metadata.CategoryIds.Contains(_options.CurrentValue.RingCentralEngageBotControlledThreadCategoryId, StringComparer.OrdinalIgnoreCase))
                    {
                        return(new Tuple <RingCentralHandledEvent, Activity>(RingCentralHandledEvent.ContentImported, newMessageActivity));
                    }
                }

                break;
            }

            case RingCentralEngageAction ringCentralAction:
            {
                switch (ringCentralAction.Action)
                {
                case RingCentralEventDescription.MessageCreate:
                {
                    var conversationRef = RingCentralSdkHelper.ConversationReferenceFromForeignThread(ringCentralAction.Params?.ThreadId, _options.CurrentValue.BotId);
                    var humanActivity   = RingCentralSdkHelper.RingCentralAgentResponseActivity(ringCentralAction.Params?.Body);
                    _logger.LogTrace($"GetActivityFromRingCentralRequestAsync: ForeignThreadId: {ringCentralAction.Params?.ThreadId}, ConversationId: {conversationRef.Conversation.Id}, ChannelId: {conversationRef.ChannelId}, ServiceUrl: {conversationRef.ServiceUrl}");

                    // Use the botAdapter to send this agent (proactive) message through to the end user
                    await((IAdapterIntegration)botAdapter).ContinueConversationAsync(
                        _options.CurrentValue.MicrosoftAppId,
                        conversationRef,
                        async(ITurnContext turnContext, CancellationToken cancellationToken) =>
                            {
                                MicrosoftAppCredentials.TrustServiceUrl(conversationRef.ServiceUrl);
                                await turnContext.SendActivityAsync(humanActivity);
                            }, default);

                    object res = new
                    {
                        id   = ringCentralAction.Params.InReplyToId,
                        body = ringCentralAction.Params.Body
                    };
                    var rbody = JsonSerializer.Serialize(res);

                    response.StatusCode = (int)HttpStatusCode.OK;
                    await response.WriteAsync(rbody);

                    return(new Tuple <RingCentralHandledEvent, Activity>(RingCentralHandledEvent.Action, humanActivity));
                }

                case RingCentralEventDescription.ImplementationInfo:
                {
                    // Return implementation info response
                    // https://github.com/ringcentral/engage-digital-source-sdk/wiki/Request-Response
                    // https://github.com/ringcentral/engage-digital-source-sdk/wiki/Actions-details
                    response.StatusCode = (int)HttpStatusCode.OK;

                    var implementationResponse = RingCentralSdkHelper.ImplementationInfoResponse();
                    var rbody = JsonSerializer.SerializeToUtf8Bytes(implementationResponse);
                    response.ContentType = "application/json";
                    await response.Body.WriteAsync(rbody);

                    return(new Tuple <RingCentralHandledEvent, Activity>(RingCentralHandledEvent.Action, null));
                }

                case RingCentralEventDescription.MessageList:
                case RingCentralEventDescription.PrivateMessagesList:
                case RingCentralEventDescription.ThreadsList:
                {
                    response.StatusCode = (int)HttpStatusCode.OK;
                    return(new Tuple <RingCentralHandledEvent, Activity>(RingCentralHandledEvent.Action, null));
                }

                case RingCentralEventDescription.PrivateMessagesShow:
                case RingCentralEventDescription.ThreadsShow:
                {
                    response.StatusCode = (int)HttpStatusCode.OK;
                    return(new Tuple <RingCentralHandledEvent, Activity>(RingCentralHandledEvent.Action, null));
                }

                default:
                    break;
                }
            }
            break;

            default:
                break;
            }
            return(new Tuple <RingCentralHandledEvent, Activity>(RingCentralHandledEvent.Unknown, null));
        }
        /// <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;
            }
        }