Add SpeakText and StartRecording activities

This commit is contained in:
Sipke Schoorstra 2021-03-29 12:04:13 +02:00
parent f1c80b5fad
commit 326129e45a
9 changed files with 275 additions and 1 deletions

View file

@ -0,0 +1,110 @@
using System;
using System.Threading.Tasks;
using Elsa.Activities.Telnyx.Client.Models;
using Elsa.Activities.Telnyx.Client.Services;
using Elsa.Activities.Telnyx.Extensions;
using Elsa.ActivityResults;
using Elsa.Attributes;
using Elsa.Design;
using Elsa.Exceptions;
using Elsa.Services;
using Elsa.Services.Models;
using Refit;
namespace Elsa.Activities.Telnyx.Activities
{
[Action(
Category = Constants.Category,
Description = "Convert text to speech and play it back on the call.",
Outcomes = new[] { OutcomeNames.Done, TelnyxOutcomeNames.CallIsNoLongerActive },
DisplayName = "Speak Text"
)]
public class SpeakText : Activity
{
private readonly ITelnyxClient _telnyxClient;
public SpeakText(ITelnyxClient telnyxClient) => _telnyxClient = telnyxClient;
[ActivityProperty(
Label = "Call Control ID",
Hint = "Unique identifier and token for controlling the call",
Category = PropertyCategories.Advanced
)]
public string CallControlId { get; set; } = default!;
[ActivityProperty(
Hint = "The language you want spoken.",
UIHint = ActivityPropertyUIHints.Dropdown,
Options = new[] { "en-US", "en-AU", "nl-NL", "es-ES", "ru-RU" }
)]
public string Language { get; set; } = default!;
[ActivityProperty(
Hint = "The gender of the voice used to speak back the text.",
UIHint = ActivityPropertyUIHints.Dropdown,
Options = new[] { "female", "male" }
)]
public string Voice { get; set; } = default!;
[ActivityProperty(Hint = "The text or SSML to be converted into speech. There is a 5,000 character limit.")]
public string Payload { get; set; }
[ActivityProperty(
Hint = "The type of the provided payload. The payload can either be plain text, or Speech Synthesis Markup Language (SSML).",
UIHint = ActivityPropertyUIHints.Dropdown,
Options = new[] { "", "text", "ssml" }
)]
public string? PayloadType { get; set; }
[ActivityProperty(
Hint = "This parameter impacts speech quality, language options and payload types. When using `basic`, only the `en-US` language and payload type `text` are allowed.",
UIHint = ActivityPropertyUIHints.Dropdown,
Options = new[] { "", "basic", "premium" },
Category = PropertyCategories.Advanced
)]
public string? ServiceLevel { get; set; }
[ActivityProperty(
Hint = "Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string.",
Category = PropertyCategories.Advanced
)]
public string? ClientState { get; set; }
[ActivityProperty(
Label = "Command ID",
Hint = "Use this field to avoid duplicate commands. Telnyx will ignore commands with the same Command ID.",
Category = PropertyCategories.Advanced
)]
public string? CommandId { get; set; }
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
{
var request = new SpeakTextRequest(
Language,
Voice,
Payload,
EmptyToNull(PayloadType),
EmptyToNull(ClientState),
EmptyToNull(CommandId),
EmptyToNull(ServiceLevel),
null
);
var callControlId = CallControlId;
try
{
await _telnyxClient.Calls.SpeakTextAsync(callControlId, request, context.CancellationToken);
return Done();
}
catch (ApiException e)
{
if (await e.CallIsNoLongerActiveAsync())
return Outcome(TelnyxOutcomeNames.CallIsNoLongerActive);
throw new WorkflowException(e.Content ?? e.Message, e);
}
}
private static string? EmptyToNull(string? value) => value is "" ? null : value;
}
}

View file

@ -0,0 +1,91 @@
using System.Threading.Tasks;
using Elsa.Activities.Telnyx.Client.Models;
using Elsa.Activities.Telnyx.Client.Services;
using Elsa.Activities.Telnyx.Extensions;
using Elsa.ActivityResults;
using Elsa.Attributes;
using Elsa.Design;
using Elsa.Exceptions;
using Elsa.Services;
using Elsa.Services.Models;
using Refit;
namespace Elsa.Activities.Telnyx.Activities
{
[Action(
Category = Constants.Category,
Description = "Start recording the call.",
Outcomes = new[] { OutcomeNames.Done, TelnyxOutcomeNames.CallIsNoLongerActive },
DisplayName = "Start Recording"
)]
public class StartRecording : Activity
{
private readonly ITelnyxClient _telnyxClient;
public StartRecording(ITelnyxClient telnyxClient) => _telnyxClient = telnyxClient;
[ActivityProperty(
Label = "Call Control ID",
Hint = "Unique identifier and token for controlling the call",
Category = PropertyCategories.Advanced
)]
public string CallControlId { get; set; } = default!;
[ActivityProperty(
Hint = "When 'dual', final audio file will be stereo recorded with the first leg on channel A, and the rest on channel B.",
UIHint = ActivityPropertyUIHints.Dropdown,
Options = new[] { "single", "dual" }
)]
public string Channels { get; set; } = default!;
[ActivityProperty(
Hint = "The audio file format used when storing the call recording. Can be either 'mp3' or 'wav'.",
UIHint = ActivityPropertyUIHints.Dropdown,
Options = new[] { "wav", "mp3" }
)]
public string Format { get; set; } = default!;
[ActivityProperty(
Hint = "Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string.",
Category = PropertyCategories.Advanced
)]
public string? ClientState { get; set; }
[ActivityProperty(
Label = "Command ID",
Hint = "Use this field to avoid duplicate commands. Telnyx will ignore commands with the same Command ID.",
Category = PropertyCategories.Advanced
)]
public string? CommandId { get; set; }
[ActivityProperty(Hint = "If enabled, a beep sound will be played at the start of a recording.")]
public bool? PlayBeep { get; set; }
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
{
var request = new StartRecordingRequest(
Channels,
Format,
EmptyToNull(ClientState),
EmptyToNull(CommandId),
PlayBeep
);
var callControlId = CallControlId;
try
{
await _telnyxClient.Calls.StartRecordingAsync(callControlId, request, context.CancellationToken);
return Done();
}
catch (ApiException e)
{
if (await e.CallIsNoLongerActiveAsync())
return Outcome(TelnyxOutcomeNames.CallIsNoLongerActive);
throw new WorkflowException(e.Content ?? e.Message, e);
}
}
private static string? EmptyToNull(string? value) => value is "" ? null : value;
}
}

View file

@ -71,5 +71,25 @@ namespace Elsa.Activities.Telnyx.Client.Models
string? CommandId,
object? Loop,
bool Overlay,
string? TargetLegs);
string? TargetLegs
);
public record StartRecordingRequest(
string Channels,
string Format,
string? ClientState,
string? CommandId,
bool? PlayBeep
);
public record SpeakTextRequest(
string Language,
string Voice,
string Payload,
string? PayloadType,
string? ClientState,
string? CommandId,
string? ServiceLevel,
string? Stop
);
}

View file

@ -27,5 +27,11 @@ namespace Elsa.Activities.Telnyx.Client.Services
[Post("/v2/calls/{callControlId}/actions/playback_start")]
Task PlayAudioAsync(string callControlId, [Body]PlayAudioRequest request, CancellationToken cancellationToken = default);
[Post("/v2/calls/{callControlId}/actions/record_start")]
Task StartRecordingAsync(string callControlId, [Body]StartRecordingRequest request, CancellationToken cancellationToken = default);
[Post("/v2/calls/{callControlId}/actions/speak")]
Task SpeakTextAsync(string callControlId, [Body]SpeakTextRequest request, CancellationToken cancellationToken = default);
}
}

View file

@ -28,6 +28,10 @@ namespace Elsa.Activities.Telnyx.Liquid
memberAccessStrategy.Register<CallPlayback>();
memberAccessStrategy.Register<CallPlaybackEndedPayload>();
memberAccessStrategy.Register<CallPlaybackStartedPayload>();
memberAccessStrategy.Register<CallRecordingSaved>();
memberAccessStrategy.Register<CallRecordingUrls>();
memberAccessStrategy.Register<CallSpeakStarted>();
memberAccessStrategy.Register<CallSpeakEnded>();
memberAccessStrategy.Register<DialResponse>();
memberAccessStrategy.Register<ErrorResponse>();

View file

@ -0,0 +1,17 @@
using Elsa.Activities.Telnyx.Webhooks.Attributes;
using NodaTime;
namespace Elsa.Activities.Telnyx.Webhooks.Payloads.Call
{
[Webhook(EventType, ActivityTypeName, "Call Recording Saved", "Triggered when a recording has been saved.")]
public sealed record CallRecordingSaved : CallPayload
{
public const string EventType = "call.recording.saved";
public const string ActivityTypeName = "CallRecordingSaved";
public string Channels { get; set; }
public CallRecordingUrls PublicRecordingUrls { get; set; }
public CallRecordingUrls RecordingUrls { get; set; }
public Instant RecordingEndedAt { get; set; }
public Instant RecordingStartedAt { get; set; }
}
}

View file

@ -0,0 +1,4 @@
namespace Elsa.Activities.Telnyx.Webhooks.Payloads.Call
{
public record CallRecordingUrls(string? Wav, string? Mp3);
}

View file

@ -0,0 +1,11 @@
using Elsa.Activities.Telnyx.Webhooks.Attributes;
namespace Elsa.Activities.Telnyx.Webhooks.Payloads.Call
{
[Webhook(EventType, ActivityTypeName, "Call Speak Ended", "Triggered when speaking has ended.")]
public sealed record CallSpeakEnded : CallPayload
{
public const string EventType = "call.speak.ended";
public const string ActivityTypeName = "CallSpeakEnded";
}
}

View file

@ -0,0 +1,11 @@
using Elsa.Activities.Telnyx.Webhooks.Attributes;
namespace Elsa.Activities.Telnyx.Webhooks.Payloads.Call
{
[Webhook(EventType, ActivityTypeName, "Call Speak Started", "Triggered when speaking has started.")]
public sealed record CallSpeakStarted : CallPayload
{
public const string EventType = "call.speak.started";
public const string ActivityTypeName = "CallSpeakStarted";
}
}