diff --git a/packages/Telephony/Actions/PauseRecording.cs b/packages/Telephony/Actions/PauseRecording.cs new file mode 100644 index 0000000000..eaf2a1adf1 --- /dev/null +++ b/packages/Telephony/Actions/PauseRecording.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Bot.Components.Telephony.Actions +{ + using System; + using System.Runtime.CompilerServices; + using System.Threading; + using System.Threading.Tasks; + using AdaptiveExpressions.Properties; + using Microsoft.Bot.Builder; + using Microsoft.Bot.Builder.Dialogs; + using Microsoft.Bot.Components.Telephony.Common; + using Microsoft.Bot.Connector; + using Microsoft.Bot.Schema; + using Microsoft.Bot.Schema.Telephony; + using Newtonsoft.Json; + + /// + /// Stops recording the current conversation. + /// + public class PauseRecording : CommandDialog + { + private const string RecordingPause = "channel/vnd.microsoft.telephony.recording.pause"; + + /// + /// Class identifier. + /// + [JsonProperty("$kind")] + public const string Kind = "Microsoft.Telephony.PauseRecording"; + + /// + /// Initializes a new instance of the class. + /// + /// Optional, source file full path. + /// Optional, line number in source file. + [JsonConstructor] + public PauseRecording([CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) + : base() + { + // enable instances of this command as debug break point + this.RegisterSourceLocation(sourceFilePath, sourceLineNumber); + + this.Name = RecordingPause; + } + + public async override Task BeginDialogAsync(DialogContext dc, object options = null, CancellationToken cancellationToken = default) + { + if (dc.Context.Activity.ChannelId == Channels.Telephony) + { + return await base.BeginDialogAsync(dc, options, cancellationToken).ConfigureAwait(false); + } + + return await dc.EndDialogAsync(cancellationToken: cancellationToken).ConfigureAwait(false); + } + + public override async Task ContinueDialogAsync(DialogContext dc, CancellationToken cancellationToken = default) + { + // TODO: Carlos try to delete + if (dc.Context.Activity.ChannelId == Channels.Telephony) + { + return await base.ContinueDialogAsync(dc, cancellationToken).ConfigureAwait(false); + } + + return await dc.EndDialogAsync(cancellationToken: cancellationToken).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/packages/Telephony/Actions/ResumeRecording.cs b/packages/Telephony/Actions/ResumeRecording.cs new file mode 100644 index 0000000000..c61c4242bc --- /dev/null +++ b/packages/Telephony/Actions/ResumeRecording.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Bot.Components.Telephony.Actions +{ + using System; + using System.Runtime.CompilerServices; + using System.Threading; + using System.Threading.Tasks; + using AdaptiveExpressions.Properties; + using Microsoft.Bot.Builder; + using Microsoft.Bot.Builder.Dialogs; + using Microsoft.Bot.Components.Telephony.Common; + using Microsoft.Bot.Connector; + using Microsoft.Bot.Schema; + using Microsoft.Bot.Schema.Telephony; + using Newtonsoft.Json; + + /// + /// Resume recording the current conversation. + /// + public class ResumeRecording : CommandDialog + { + public const string RecordingResume = "channel/vnd.microsoft.telephony.recording.resume"; + + /// + /// Class identifier. + /// + [JsonProperty("$kind")] + public const string Kind = "Microsoft.Telephony.PauseRecording"; + + /// + /// Initializes a new instance of the class. + /// + /// Optional, source file full path. + /// Optional, line number in source file. + [JsonConstructor] + public ResumeRecording([CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) + : base() + { + // enable instances of this command as debug break point + this.RegisterSourceLocation(sourceFilePath, sourceLineNumber); + + this.Name = RecordingResume; + } + + public async override Task BeginDialogAsync(DialogContext dc, object options = null, CancellationToken cancellationToken = default) + { + if (dc.Context.Activity.ChannelId == Channels.Telephony) + { + return await base.BeginDialogAsync(dc, options, cancellationToken).ConfigureAwait(false); + } + + return await dc.EndDialogAsync(cancellationToken: cancellationToken).ConfigureAwait(false); + } + + public override async Task ContinueDialogAsync(DialogContext dc, CancellationToken cancellationToken = default) + { + // TODO: Carlos try to delete + if (dc.Context.Activity.ChannelId == Channels.Telephony) + { + return await base.ContinueDialogAsync(dc, cancellationToken).ConfigureAwait(false); + } + + return await dc.EndDialogAsync(cancellationToken: cancellationToken).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/packages/Telephony/Actions/StartRecording.cs b/packages/Telephony/Actions/StartRecording.cs new file mode 100644 index 0000000000..44100de849 --- /dev/null +++ b/packages/Telephony/Actions/StartRecording.cs @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +namespace Microsoft.Bot.Components.Telephony.Actions +{ + using System; + using System.Runtime.CompilerServices; + using System.Threading; + using System.Threading.Tasks; + using AdaptiveExpressions.Properties; + using Microsoft.Bot.Builder; + using Microsoft.Bot.Builder.Dialogs; + using Microsoft.Bot.Components.Telephony.Common; + using Microsoft.Bot.Connector; + using Microsoft.Bot.Schema; + using Microsoft.Bot.Schema.Telephony; + using Newtonsoft.Json; + + /// + /// Stars recording the current conversation. + /// + public class StartRecording : CommandDialog + { + private const string RecordingStart = "channel/vnd.microsoft.telephony.recording.start"; + + /// + /// Class identifier. + /// + [JsonProperty("$kind")] + public const string Kind = "Microsoft.Telephony.StartRecording"; + + /// + /// Initializes a new instance of the class. + /// + /// Optional, source file full path. + /// Optional, line number in source file. + [JsonConstructor] + public StartRecording([CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) + : base() + { + // enable instances of this command as debug break point + this.RegisterSourceLocation(sourceFilePath, sourceLineNumber); + + this.Name = RecordingStart; + + this.Data = new RecordingStartSettings() + { + RecordingChannelType = RecordingChannelType.Mixed, + RecordingContentType = RecordingContentType.AudioVideo, + }; + } + + public async override Task BeginDialogAsync(DialogContext dc, object options = null, CancellationToken cancellationToken = default) + { + if (dc.Context.Activity.ChannelId == Channels.Telephony) + { + return await base.BeginDialogAsync(dc, options, cancellationToken).ConfigureAwait(false); + } + + return await dc.EndDialogAsync(cancellationToken: cancellationToken).ConfigureAwait(false); + } + + public override async Task ContinueDialogAsync(DialogContext dc, CancellationToken cancellationToken = default) + { + // TODO: Carlos try to delete + if (dc.Context.Activity.ChannelId == Channels.Telephony) + { + return await base.ContinueDialogAsync(dc, cancellationToken).ConfigureAwait(false); + } + + return await dc.EndDialogAsync(cancellationToken: cancellationToken).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/packages/Telephony/Common/CommandDialog.cs b/packages/Telephony/Common/CommandDialog.cs new file mode 100644 index 0000000000..4624dedbe3 --- /dev/null +++ b/packages/Telephony/Common/CommandDialog.cs @@ -0,0 +1,110 @@ +using AdaptiveExpressions.Properties; +using Microsoft.Bot.Builder; +using Microsoft.Bot.Builder.Dialogs; +using Microsoft.Bot.Schema; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Bot.Components.Telephony.Common +{ + public class CommandDialog : CommandDialog + { + + } + + public class CommandDialog : Dialog + { + /// + /// Gets or sets the phone number to be included when sending the handoff activity. + /// + /// + /// . + /// + protected StringExpression Name { get; set; } + + /// + /// Gets or sets the phone number to be included when sending the handoff activity. + /// + /// + /// . + /// + protected T Data { get; set; } + + public override async Task BeginDialogAsync(DialogContext dc, object options = null, CancellationToken cancellationToken = default) + { + // TODO: check name not null / expression has value + + var startRecordingActivity = CreateCommandActivity(dc.Context, this.Data, this.Name.GetValue(dc.State)); + + var response = await dc.Context.SendActivityAsync(startRecordingActivity, cancellationToken).ConfigureAwait(false); + + return new DialogTurnResult(DialogTurnStatus.Waiting, startRecordingActivity.Name); + } + + public override async Task ContinueDialogAsync(DialogContext dc, CancellationToken cancellationToken = default) + { + // check activity + var activity = dc.Context.Activity; + + // If command result, handle it + if (activity.Type == ActivityTypes.CommandResult + && activity.Name == this.Name.GetValue(dc.State)) + { + var commandResult = TelephonyExtensions.GetCommandResultValue(activity); + + if (commandResult.Error != null) + { + throw new ErrorResponseException($"{commandResult.Error.Code}: {commandResult.Error.Message}"); + } + + // TODO: correlate command ids + + return new DialogTurnResult(DialogTurnStatus.Complete); + } + + // for now, end turn and keep waiting + // TODO: Carlos add interruption model + return new DialogTurnResult(DialogTurnStatus.Waiting); + } + + private static Activity CreateCommandActivity(ITurnContext turnContext, T data, string name) + { + if (turnContext == null) + { + throw new ArgumentNullException(nameof(turnContext)); + } + + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentNullException(nameof(name)); + } + + var commandActivity = new Activity(type: ActivityTypes.Command); + + commandActivity.Name = name; + + var commandValue = new CommandValue() + { + CommandId = Guid.NewGuid().ToString(), + Data = data, + }; + + commandActivity.Value = commandValue; + + commandActivity.From = turnContext.Activity.From; + + // TODO: Check with SDK crew + //commandActivity.RelatesTo = turnContext.Activity.GetConversationReference(); + + commandActivity.ReplyToId = turnContext.Activity.Id; + commandActivity.ServiceUrl = turnContext.Activity.ServiceUrl; + commandActivity.ChannelId = turnContext.Activity.ChannelId; + + return commandActivity; + } + } +} diff --git a/packages/Telephony/Common/RecordingStartSettings.cs b/packages/Telephony/Common/RecordingStartSettings.cs new file mode 100644 index 0000000000..9e9c7a8d22 --- /dev/null +++ b/packages/Telephony/Common/RecordingStartSettings.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.Bot.Schema.Telephony +{ + public enum RecordingContentType + { + Audio, + AudioVideo + } + + public enum RecordingChannelType + { + Mixed, + Unmixed + } + + public class RecordingStartSettings + { + public RecordingContentType RecordingContentType { get; set; } + public RecordingChannelType RecordingChannelType { get; set; } + } +} \ No newline at end of file diff --git a/packages/Telephony/Common/TelephonyExtensions.cs b/packages/Telephony/Common/TelephonyExtensions.cs new file mode 100644 index 0000000000..c52b2cab8c --- /dev/null +++ b/packages/Telephony/Common/TelephonyExtensions.cs @@ -0,0 +1,63 @@ +using Microsoft.Bot.Builder; +using Microsoft.Bot.Schema; +using Newtonsoft.Json.Linq; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Bot.Components.Telephony.Common +{ + public static class TelephonyExtensions + { + /// + /// Gets the CommandValue from the activity. + /// + /// The command activity + /// Gets the CommandValue from the activity + public static CommandValue GetCommandValue(this ICommandActivity activity) + { + object value = activity?.Value; + + if (value == null) + { + return null; + } + else if (value is CommandValue commandValue) + { + return commandValue; + } + else + { + return ((JObject)value).ToObject>(); + } + } + + public static CommandResultValue GetCommandResultValue(this ICommandResultActivity activity) + { + return GetCommandResultValue(activity); + } + + /// + /// Gets the CommandResultValue from the commmand result activity. + /// + /// The command result activity + /// Gets the CommandResultValue from the activity. + public static CommandResultValue GetCommandResultValue(this ICommandResultActivity activity) + { + object value = activity?.Value; + + if (value == null) + { + return null; + } + else if (value is CommandResultValue commandResultValue) + { + return commandResultValue; + } + else + { + return ((JObject)value).ToObject>(); + } + } + } +} \ No newline at end of file