Various Telnyx activity enhancements

This commit is contained in:
Sipke Schoorstra 2021-05-31 19:50:09 +02:00
parent c7b0b2d497
commit 3eaefab72f
6 changed files with 278 additions and 5 deletions

View file

@ -0,0 +1,135 @@
using Elsa.Activities.ControlFlow;
using Elsa.Activities.Telnyx.Webhooks.Payloads.Call;
using Elsa.Attributes;
using Elsa.Builders;
using Elsa.Design;
using Elsa.Expressions;
using Elsa.Services;
namespace Elsa.Activities.Telnyx.Activities
{
[Action(
Category = Constants.Category,
Description = "Convert text to speech and play it back on the call. When done speaking, control is returned to the workflow.",
Outcomes = new[] {OutcomeNames.Done, TelnyxOutcomeNames.CallIsNoLongerActive},
DisplayName = "Speak Text And Wait"
)]
public class SpeakAndWait : CompositeActivity
{
[ActivityInput(
Label = "Call Control ID",
Hint = "Unique identifier and token for controlling the call",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid}
)]
public string? CallControlId
{
get => GetState<string>();
set => SetState(value);
}
[ActivityInput(
Hint = "The language you want spoken.",
UIHint = ActivityInputUIHints.Dropdown,
Options = new[] {"en-US", "en-AU", "nl-NL", "es-ES", "ru-RU"},
DefaultValue = "en-US",
SupportedSyntaxes = new[] {SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid}
)]
public string Language
{
get => GetState<string>(() => "en-US");
set => SetState(value);
}
[ActivityInput(
Hint = "The gender of the voice used to speak back the text.",
UIHint = ActivityInputUIHints.Dropdown,
Options = new[] {"female", "male"},
DefaultValue = "female",
SupportedSyntaxes = new[] {SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid}
)]
public string Voice
{
get => GetState<string>(() => "female");
set => SetState(value);
}
[ActivityInput(Hint = "The text or SSML to be converted into speech. There is a 5,000 character limit.", SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid})]
public string Payload
{
get => GetState<string>()!;
set => SetState(value);
}
[ActivityInput(
Hint = "The type of the provided payload. The payload can either be plain text, or Speech Synthesis Markup Language (SSML).",
UIHint = ActivityInputUIHints.Dropdown,
Options = new[] {"", "text", "ssml"},
SupportedSyntaxes = new[] {SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid}
)]
public string? PayloadType
{
get => GetState<string>();
set => SetState(value);
}
[ActivityInput(
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 = ActivityInputUIHints.Dropdown,
Options = new[] {"", "basic", "premium"},
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] {SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid}
)]
public string? ServiceLevel
{
get => GetState<string>();
set => SetState(value);
}
[ActivityInput(
Hint = "Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string.",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid}
)]
public string? ClientState
{
get => GetState<string>();
set => SetState(value);
}
[ActivityInput(
Label = "Command ID",
Hint = "Use this field to avoid duplicate commands. Telnyx will ignore commands with the same Command ID.",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] {SyntaxNames.JavaScript, SyntaxNames.Liquid}
)]
public string? CommandId
{
get => GetState<string>();
set => SetState(value);
}
public override void Build(ICompositeActivityBuilder builder)
{
builder
.StartWith<SpeakText>(speakText => speakText
.WithLanguage(() => Language)
.WithPayload(() => Payload)
.WithPayloadType(() => PayloadType)
.WithVoice(() => Voice)
.WithClientState(() => ClientState)
.WithCommandId(() => CommandId)
.WithServiceLevel(() => ServiceLevel)
.WithCallControlId(() => CallControlId)
, speakText =>
{
speakText
.When(OutcomeNames.Done)
.ThenTypeNamed(nameof(CallSpeakEnded))
.Finish(OutcomeNames.Done);
speakText.When(TelnyxOutcomeNames.CallIsNoLongerActive).Finish(TelnyxOutcomeNames.CallIsNoLongerActive);
});
}
}
}

View file

@ -1,9 +1,11 @@
using System.Threading.Tasks;
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.Builders;
using Elsa.Design;
using Elsa.Exceptions;
using Elsa.Expressions;
@ -36,17 +38,19 @@ namespace Elsa.Activities.Telnyx.Activities
Hint = "The language you want spoken.",
UIHint = ActivityInputUIHints.Dropdown,
Options = new[] { "en-US", "en-AU", "nl-NL", "es-ES", "ru-RU" },
DefaultValue = "en-US",
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public string Language { get; set; } = default!;
public string Language { get; set; } = "en-US";
[ActivityInput(
Hint = "The gender of the voice used to speak back the text.",
UIHint = ActivityInputUIHints.Dropdown,
Options = new[] { "female", "male" },
DefaultValue = "female",
SupportedSyntaxes = new[] { SyntaxNames.Literal, SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public string Voice { get; set; } = default!;
public string Voice { get; set; } = "female";
[ActivityInput(Hint = "The text or SSML to be converted into speech. There is a 5,000 character limit.", SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid })]
public string Payload { get; set; } = default!;
@ -114,4 +118,55 @@ namespace Elsa.Activities.Telnyx.Activities
private static string? EmptyToNull(string? value) => value is "" ? null : value;
}
public static class SpeakTextExtensions
{
public static ISetupActivity<SpeakText> WithCallControlId(this ISetupActivity<SpeakText> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.CallControlId, value);
public static ISetupActivity<SpeakText> WithCallControlId(this ISetupActivity<SpeakText> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.CallControlId, value);
public static ISetupActivity<SpeakText> WithCallControlId(this ISetupActivity<SpeakText> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.CallControlId, value);
public static ISetupActivity<SpeakText> WithCallControlId(this ISetupActivity<SpeakText> setup, Func<string?> value) => setup.Set(x => x.CallControlId, value);
public static ISetupActivity<SpeakText> WithCallControlId(this ISetupActivity<SpeakText> setup, string? value) => setup.Set(x => x.CallControlId, value);
public static ISetupActivity<SpeakText> WithLanguage(this ISetupActivity<SpeakText> setup, Func<ActivityExecutionContext, ValueTask<string>> value) => setup.Set(x => x.Language, value!);
public static ISetupActivity<SpeakText> WithLanguage(this ISetupActivity<SpeakText> setup, Func<ActivityExecutionContext, string> value) => setup.Set(x => x.Language, value);
public static ISetupActivity<SpeakText> WithLanguage(this ISetupActivity<SpeakText> setup, Func<ValueTask<string>> value) => setup.Set(x => x.Language, value!);
public static ISetupActivity<SpeakText> WithLanguage(this ISetupActivity<SpeakText> setup, Func<string> value) => setup.Set(x => x.Language, value);
public static ISetupActivity<SpeakText> WithLanguage(this ISetupActivity<SpeakText> setup, string value) => setup.Set(x => x.Language, value);
public static ISetupActivity<SpeakText> WithVoice(this ISetupActivity<SpeakText> setup, Func<ActivityExecutionContext, ValueTask<string>> value) => setup.Set(x => x.Voice, value!);
public static ISetupActivity<SpeakText> WithVoice(this ISetupActivity<SpeakText> setup, Func<ActivityExecutionContext, string> value) => setup.Set(x => x.Voice, value);
public static ISetupActivity<SpeakText> WithVoice(this ISetupActivity<SpeakText> setup, Func<ValueTask<string>> value) => setup.Set(x => x.Voice, value!);
public static ISetupActivity<SpeakText> WithVoice(this ISetupActivity<SpeakText> setup, Func<string> value) => setup.Set(x => x.Voice, value);
public static ISetupActivity<SpeakText> WithVoice(this ISetupActivity<SpeakText> setup, string value) => setup.Set(x => x.Voice, value);
public static ISetupActivity<SpeakText> WithPayload(this ISetupActivity<SpeakText> setup, Func<ActivityExecutionContext, ValueTask<string>> value) => setup.Set(x => x.Payload, value!);
public static ISetupActivity<SpeakText> WithPayload(this ISetupActivity<SpeakText> setup, Func<ActivityExecutionContext, string> value) => setup.Set(x => x.Payload, value);
public static ISetupActivity<SpeakText> WithPayload(this ISetupActivity<SpeakText> setup, Func<ValueTask<string>> value) => setup.Set(x => x.Payload, value!);
public static ISetupActivity<SpeakText> WithPayload(this ISetupActivity<SpeakText> setup, Func<string> value) => setup.Set(x => x.Payload, value);
public static ISetupActivity<SpeakText> WithPayload(this ISetupActivity<SpeakText> setup, string value) => setup.Set(x => x.Payload, value);
public static ISetupActivity<SpeakText> WithPayloadType(this ISetupActivity<SpeakText> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.PayloadType, value);
public static ISetupActivity<SpeakText> WithPayloadType(this ISetupActivity<SpeakText> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.PayloadType, value);
public static ISetupActivity<SpeakText> WithPayloadType(this ISetupActivity<SpeakText> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.PayloadType, value);
public static ISetupActivity<SpeakText> WithPayloadType(this ISetupActivity<SpeakText> setup, Func<string?> value) => setup.Set(x => x.PayloadType, value);
public static ISetupActivity<SpeakText> WithPayloadType(this ISetupActivity<SpeakText> setup, string? value) => setup.Set(x => x.PayloadType, value);
public static ISetupActivity<SpeakText> WithServiceLevel(this ISetupActivity<SpeakText> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.ServiceLevel, value);
public static ISetupActivity<SpeakText> WithServiceLevel(this ISetupActivity<SpeakText> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.ServiceLevel, value);
public static ISetupActivity<SpeakText> WithServiceLevel(this ISetupActivity<SpeakText> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.ServiceLevel, value);
public static ISetupActivity<SpeakText> WithServiceLevel(this ISetupActivity<SpeakText> setup, Func<string?> value) => setup.Set(x => x.ServiceLevel, value);
public static ISetupActivity<SpeakText> WithServiceLevel(this ISetupActivity<SpeakText> setup, string? value) => setup.Set(x => x.ServiceLevel, value);
public static ISetupActivity<SpeakText> WithClientState(this ISetupActivity<SpeakText> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.ClientState, value);
public static ISetupActivity<SpeakText> WithClientState(this ISetupActivity<SpeakText> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.ClientState, value);
public static ISetupActivity<SpeakText> WithClientState(this ISetupActivity<SpeakText> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.ClientState, value);
public static ISetupActivity<SpeakText> WithClientState(this ISetupActivity<SpeakText> setup, Func<string?> value) => setup.Set(x => x.ClientState, value);
public static ISetupActivity<SpeakText> WithClientState(this ISetupActivity<SpeakText> setup, string? value) => setup.Set(x => x.ClientState, value);
public static ISetupActivity<SpeakText> WithCommandId(this ISetupActivity<SpeakText> setup, Func<ActivityExecutionContext, ValueTask<string?>> value) => setup.Set(x => x.CommandId, value);
public static ISetupActivity<SpeakText> WithCommandId(this ISetupActivity<SpeakText> setup, Func<ActivityExecutionContext, string?> value) => setup.Set(x => x.CommandId, value);
public static ISetupActivity<SpeakText> WithCommandId(this ISetupActivity<SpeakText> setup, Func<ValueTask<string?>> value) => setup.Set(x => x.CommandId, value);
public static ISetupActivity<SpeakText> WithCommandId(this ISetupActivity<SpeakText> setup, Func<string?> value) => setup.Set(x => x.CommandId, value);
public static ISetupActivity<SpeakText> WithCommandId(this ISetupActivity<SpeakText> setup, string? value) => setup.Set(x => x.CommandId, value);
}
}

View file

@ -0,0 +1,75 @@
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.Expressions;
using Elsa.Services;
using Elsa.Services.Models;
using Refit;
namespace Elsa.Activities.Telnyx.Activities
{
[Action(
Category = Constants.Category,
Description = "Stop recording the call.",
Outcomes = new[] { OutcomeNames.Done, TelnyxOutcomeNames.CallIsNoLongerActive },
DisplayName = "Stop Recording"
)]
public class StopRecording : Activity
{
private readonly ITelnyxClient _telnyxClient;
public StopRecording(ITelnyxClient telnyxClient) => _telnyxClient = telnyxClient;
[ActivityInput(
Label = "Call Control ID",
Hint = "Unique identifier and token for controlling the call",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public string? CallControlId { get; set; } = default!;
[ActivityInput(
Hint = "Use this field to add state to every subsequent webhook. It must be a valid Base-64 encoded string.",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public string? ClientState { get; set; }
[ActivityInput(
Label = "Command ID",
Hint = "Use this field to avoid duplicate commands. Telnyx will ignore commands with the same Command ID.",
Category = PropertyCategories.Advanced,
SupportedSyntaxes = new[] { SyntaxNames.JavaScript, SyntaxNames.Liquid }
)]
public string? CommandId { get; set; }
protected override async ValueTask<IActivityExecutionResult> OnExecuteAsync(ActivityExecutionContext context)
{
var request = new StopRecordingRequest(
EmptyToNull(ClientState),
EmptyToNull(CommandId)
);
var callControlId = context.GetCallControlId(CallControlId);
try
{
await _telnyxClient.Calls.StopRecordingAsync(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

@ -38,7 +38,7 @@ namespace Elsa.Activities.Telnyx.Activities
Model = webhookModel;
Payload = webhookModel.Data.Payload;
return Done();
return Done(webhookModel);
}
}
}

View file

@ -81,7 +81,12 @@ namespace Elsa.Activities.Telnyx.Client.Models
string? CommandId,
bool? PlayBeep
);
public record StopRecordingRequest(
string? ClientState,
string? CommandId
);
public record SpeakTextRequest(
string Language,
string Voice,

View file

@ -31,6 +31,9 @@ namespace Elsa.Activities.Telnyx.Client.Services
[Post("/v2/calls/{callControlId}/actions/record_start")]
Task StartRecordingAsync(string callControlId, [Body]StartRecordingRequest request, CancellationToken cancellationToken = default);
[Post("/v2/calls/{callControlId}/actions/record_stop")]
Task StopRecordingAsync(string callControlId, [Body]StopRecordingRequest request, CancellationToken cancellationToken = default);
[Post("/v2/calls/{callControlId}/actions/speak")]
Task SpeakTextAsync(string callControlId, [Body]SpeakTextRequest request, CancellationToken cancellationToken = default);
}