diff --git a/Elsa.sln b/Elsa.sln index 34eb44ccf..d206732dd 100644 --- a/Elsa.sln +++ b/Elsa.sln @@ -76,8 +76,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Common", "src\modules\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.EntityFrameworkCore", "src\modules\Elsa.EntityFrameworkCore\Elsa.EntityFrameworkCore.csproj", "{7CC95512-7F44-45C3-A669-95B7048D4730}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Telnyx", "src\modules\Elsa.Telnyx\Elsa.Telnyx.csproj", "{F09B30F7-AF33-44D7-B835-099D1F72A37E}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elsa.Email", "src\modules\Elsa.Email\Elsa.Email.csproj", "{1E5BD8D9-55BE-41E2-B389-6AB9F26C22AF}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker", "docker", "{986E5482-0482-448C-B9E4-EC67A9474B85}" @@ -511,10 +509,6 @@ Global {7CC95512-7F44-45C3-A669-95B7048D4730}.Debug|Any CPU.Build.0 = Debug|Any CPU {7CC95512-7F44-45C3-A669-95B7048D4730}.Release|Any CPU.ActiveCfg = Release|Any CPU {7CC95512-7F44-45C3-A669-95B7048D4730}.Release|Any CPU.Build.0 = Release|Any CPU - {F09B30F7-AF33-44D7-B835-099D1F72A37E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F09B30F7-AF33-44D7-B835-099D1F72A37E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F09B30F7-AF33-44D7-B835-099D1F72A37E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F09B30F7-AF33-44D7-B835-099D1F72A37E}.Release|Any CPU.Build.0 = Release|Any CPU {1E5BD8D9-55BE-41E2-B389-6AB9F26C22AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1E5BD8D9-55BE-41E2-B389-6AB9F26C22AF}.Debug|Any CPU.Build.0 = Debug|Any CPU {1E5BD8D9-55BE-41E2-B389-6AB9F26C22AF}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -926,7 +920,6 @@ Global {30B49E42-AE61-4F7C-981F-923F07BB3986} = {5948B0A5-7873-4DBB-BA03-EB283D6EA91B} {D229105F-6879-4452-9189-75DE060C0F4C} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} {7CC95512-7F44-45C3-A669-95B7048D4730} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} - {F09B30F7-AF33-44D7-B835-099D1F72A37E} = {EBD0CF78-C5D7-4B4B-94C9-C5D8C20B6F59} {1E5BD8D9-55BE-41E2-B389-6AB9F26C22AF} = {EBD0CF78-C5D7-4B4B-94C9-C5D8C20B6F59} {3246883E-2FA7-4B4A-BDC5-99039A2869BC} = {9B4F139F-7D26-435C-A561-89E65A67A8E5} {2BBFDE36-28A7-4875-8EBE-CC25C76B97FF} = {EBD0CF78-C5D7-4B4B-94C9-C5D8C20B6F59} diff --git a/src/modules/Elsa.Telnyx/Activities/AnswerCall.cs b/src/modules/Elsa.Telnyx/Activities/AnswerCall.cs deleted file mode 100644 index f26970cc3..000000000 --- a/src/modules/Elsa.Telnyx/Activities/AnswerCall.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Workflows; -using Elsa.Workflows.Attributes; - -namespace Elsa.Telnyx.Activities; - -/// -public class AnswerCall : AnswerCallBase -{ - /// - public AnswerCall([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - /// The activity to schedule when the call was successfully answered. - /// - [Port] - public IActivity? Connected { get; set; } - - /// - /// The activity to schedule when the call was no longer active. - /// - [Port] - public IActivity? Disconnected { get; set; } - - /// - protected override async ValueTask HandleConnectedAsync(ActivityExecutionContext context) => await context.ScheduleActivityAsync(Connected); - - /// - protected override async ValueTask HandleDisconnectedAsync(ActivityExecutionContext context) => await context.ScheduleActivityAsync(Disconnected); -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/AnswerCallBase.cs b/src/modules/Elsa.Telnyx/Activities/AnswerCallBase.cs deleted file mode 100644 index 21b3d1892..000000000 --- a/src/modules/Elsa.Telnyx/Activities/AnswerCallBase.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Elsa.Extensions; -using Elsa.Telnyx.Bookmarks; -using Elsa.Telnyx.Client.Models; -using Elsa.Telnyx.Client.Services; -using Elsa.Telnyx.Extensions; -using Elsa.Telnyx.Payloads.Call; -using Elsa.Workflows; -using Elsa.Workflows.Attributes; -using Elsa.Workflows.Models; -using Refit; - -namespace Elsa.Telnyx.Activities; - -/// -/// Answer an incoming call. You must issue this command before executing subsequent commands on an incoming call. -/// -[Activity(Constants.Namespace, "Answer an incoming call. You must issue this command before executing subsequent commands on an incoming call.", Kind = ActivityKind.Task)] -public abstract class AnswerCallBase : Activity -{ - /// - protected AnswerCallBase(string? source = default, int? line = default) : base(source, line) - { - } - - /// - /// The call control ID to answer. Leave blank when the workflow is driven by an incoming call and you wish to pick up that one. - /// - [Input(DisplayName = "Call Control ID", Description = "The call control ID of the call to answer.", Category = "Advanced")] - public Input CallControlId { get; set; } = default!; - - /// - protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) - { - var callControlId = CallControlId.Get(context); - - var request = new AnswerCallRequest - { - ClientState = context.CreateCorrelatingClientState(context.Id) - }; - - var telnyxClient = context.GetRequiredService(); - - try - { - // Send a request to Telnyx to answer the call. - await telnyxClient.Calls.AnswerCallAsync(callControlId, request, context.CancellationToken); - - // Create a bookmark so we can resume the workflow when the call is answered. - context.CreateBookmark(new AnswerCallStimulus(callControlId), ResumeAsync, true); - } - catch (ApiException e) - { - if (!await e.CallIsNoLongerActiveAsync()) throw; - await HandleDisconnectedAsync(context); - } - } - - /// - /// Invoked when the call was successfully answered. - /// - protected abstract ValueTask HandleConnectedAsync(ActivityExecutionContext context); - - /// - /// Invoked when the call was no longer active. - /// - protected abstract ValueTask HandleDisconnectedAsync(ActivityExecutionContext context); - - private async ValueTask ResumeAsync(ActivityExecutionContext context) - { - var payload = context.GetWorkflowInput(); - context.Set(Result, payload); - await HandleConnectedAsync(context); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/BridgeCalls.cs b/src/modules/Elsa.Telnyx/Activities/BridgeCalls.cs deleted file mode 100644 index 573d4b84b..000000000 --- a/src/modules/Elsa.Telnyx/Activities/BridgeCalls.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Workflows; -using Elsa.Workflows.Attributes; -using JetBrains.Annotations; - -namespace Elsa.Telnyx.Activities; - -/// -[PublicAPI] -public class BridgeCalls : BridgeCallsBase -{ - /// - public BridgeCalls([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - /// The to execute when the source leg call is no longer active. - /// - [Port] - public IActivity? Disconnected { get; set; } - - /// - /// The to execute when the two calls are bridged. - /// - [Port] - public IActivity? Bridged { get; set; } - - /// - protected override async ValueTask HandleDisconnectedAsync(ActivityExecutionContext context) => await context.ScheduleActivityAsync(Disconnected, OnCompleted); - - /// - protected override async ValueTask HandleBridgedAsync(ActivityExecutionContext context) => await context.ScheduleActivityAsync(Bridged, OnCompleted); -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/BridgeCallsBase.cs b/src/modules/Elsa.Telnyx/Activities/BridgeCallsBase.cs deleted file mode 100644 index b5b6bb89f..000000000 --- a/src/modules/Elsa.Telnyx/Activities/BridgeCallsBase.cs +++ /dev/null @@ -1,98 +0,0 @@ -using Elsa.Extensions; -using Elsa.Telnyx.Bookmarks; -using Elsa.Telnyx.Client.Models; -using Elsa.Telnyx.Client.Services; -using Elsa.Telnyx.Extensions; -using Elsa.Telnyx.Models; -using Elsa.Telnyx.Payloads.Call; -using Elsa.Workflows; -using Elsa.Workflows.Attributes; -using Elsa.Workflows.Models; -using JetBrains.Annotations; -using Refit; - -namespace Elsa.Telnyx.Activities; - -/// -/// Bridge two calls. -/// -[Activity(Constants.Namespace, "Bridge two calls.", Kind = ActivityKind.Task)] -[PublicAPI] -public abstract class BridgeCallsBase : Activity -{ - /// - protected BridgeCallsBase(string? source = default, int? line = default) : base(source, line) - { - } - - /// - /// The source call control ID of one of the call to bridge with. Leave empty to use the ambient inbound call control Id, if there is one. - /// - [Input(DisplayName = "Call Control ID A", Description = "The source call control ID of one of the call to bridge with. Leave empty to use the ambient inbound call control Id, if there is one.")] - public Input CallControlIdA { get; set; } = default!; - - /// - /// The destination call control ID of the call you want to bridge with. - /// - [Input(DisplayName = "Call Control ID B", Description = "The destination call control ID of the call you want to bridge with.")] - public Input CallControlIdB { get; set; } = default!; - - /// - protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) - { - var callControlIdA = CallControlIdA.Get(context); - var callControlIdB = CallControlIdB.Get(context); - var request = new BridgeCallsRequest(callControlIdB, ClientState: context.CreateCorrelatingClientState(context.Id)); - var telnyxClient = context.GetRequiredService(); - - try - { - await telnyxClient.Calls.BridgeCallsAsync(callControlIdA, request, context.CancellationToken); - - var bookmarkA = new WebhookEventStimulus(WebhookEventTypes.CallBridged, callControlIdA); - var bookmarkB = new WebhookEventStimulus(WebhookEventTypes.CallBridged, callControlIdB); - context.CreateBookmarks(new[] { bookmarkA, bookmarkB }, ResumeAsync, false); - } - catch (ApiException e) - { - if (!await e.CallIsNoLongerActiveAsync()) throw; - - await HandleDisconnectedAsync(context); - } - } - - /// - /// Called when the call is disconnected. - /// - protected abstract ValueTask HandleDisconnectedAsync(ActivityExecutionContext context); - - /// - /// Called when the call is bridged. - /// - /// - protected abstract ValueTask HandleBridgedAsync(ActivityExecutionContext context); - - /// - /// Called when the activity is completed. - /// - protected async ValueTask OnCompleted(ActivityCompletedContext context) => await context.TargetContext.CompleteActivityAsync(); - - private async ValueTask ResumeAsync(ActivityExecutionContext context) - { - var payload = context.GetWorkflowInput()!; - var callControlIdA = CallControlIdA.Get(context); - var callControlIdB = CallControlIdB.Get(context);; - - if (payload.CallControlId == callControlIdA) context.SetProperty("CallBridgedPayloadA", payload); - if (payload.CallControlId == callControlIdB) context.SetProperty("CallBridgedPayloadB", payload); - - var callBridgedPayloadA = context.GetProperty("CallBridgedPayloadA"); - var callBridgedPayloadB = context.GetProperty("CallBridgedPayloadB"); - - if (callBridgedPayloadA != null && callBridgedPayloadB != null) - { - context.Set(Result, new BridgedCallsOutput(callBridgedPayloadA, callBridgedPayloadB)); - await HandleBridgedAsync(context); - } - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/CallAnswered.cs b/src/modules/Elsa.Telnyx/Activities/CallAnswered.cs deleted file mode 100644 index a1f5f9e20..000000000 --- a/src/modules/Elsa.Telnyx/Activities/CallAnswered.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Extensions; -using Elsa.Telnyx.Bookmarks; -using Elsa.Telnyx.Helpers; -using Elsa.Telnyx.Payloads.Call; -using Elsa.Workflows; -using Elsa.Workflows.Attributes; -using Elsa.Workflows.UIHints; -using Elsa.Workflows.Models; - -namespace Elsa.Telnyx.Activities; - -/// -/// Represents a Telnyx webhook event trigger. -/// -[Activity("Telnyx", "Telnyx", "A Telnyx webhook event that executes when a call is answered.", Kind = ActivityKind.Trigger)] -public class CallAnswered : Activity -{ - /// - public CallAnswered([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - /// A list of call control IDs to listen for. - /// - [Input(Description = "A list of call control IDs to listen for.", UIHint = InputUIHints.MultiText)] - public Input> CallControlIds { get; set; } = default!; - - /// - protected override void Execute(ActivityExecutionContext context) - { - var callControlIds = CallControlIds.Get(context); - - foreach (var callControlId in callControlIds) - { - var payload = new CallAnsweredStimulus(callControlId); - context.CreateBookmark(new CreateBookmarkArgs - { - Stimulus = payload, - Callback = Resume, - BookmarkName = Type, - IncludeActivityInstanceId = false - }); - } - } - - private async ValueTask Resume(ActivityExecutionContext context) - { - var input = context.GetWorkflowInput(WebhookSerializerOptions.Create()); - context.Set(Result, input); - await context.CompleteActivityAsync(); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/CallHangup.cs b/src/modules/Elsa.Telnyx/Activities/CallHangup.cs deleted file mode 100644 index cb77dd131..000000000 --- a/src/modules/Elsa.Telnyx/Activities/CallHangup.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Extensions; -using Elsa.Telnyx.Bookmarks; -using Elsa.Telnyx.Helpers; -using Elsa.Telnyx.Payloads.Call; -using Elsa.Workflows; -using Elsa.Workflows.Attributes; -using Elsa.Workflows.UIHints; -using Elsa.Workflows.Models; - -namespace Elsa.Telnyx.Activities; - -/// -/// Represents a Telnyx webhook event trigger. -/// -[Activity("Telnyx", "Telnyx", "A Telnyx webhook event that executes when a call is hangup.", Kind = ActivityKind.Trigger)] -public class CallHangup : Activity -{ - /// - public CallHangup([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - /// A list of call control IDs to listen for. - /// - [Input(Description = "A list of call control IDs to listen for.", UIHint = InputUIHints.MultiText)] - public Input> CallControlIds { get; set; } = default!; - - /// - protected override void Execute(ActivityExecutionContext context) - { - var callControlIds = CallControlIds.Get(context); - - foreach (var callControlId in callControlIds) - { - var payload = new CallHangupStimulus(callControlId); - context.CreateBookmark(new CreateBookmarkArgs - { - Stimulus = payload, - Callback = Resume, - BookmarkName = Type, - IncludeActivityInstanceId = false - }); - } - } - - private async ValueTask Resume(ActivityExecutionContext context) - { - var input = context.GetWorkflowInput(WebhookSerializerOptions.Create()); - context.Set(Result, input); - await context.CompleteActivityAsync(); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/Dial.cs b/src/modules/Elsa.Telnyx/Activities/Dial.cs deleted file mode 100644 index 9e5511bdd..000000000 --- a/src/modules/Elsa.Telnyx/Activities/Dial.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Extensions; -using Elsa.Telnyx.Client.Models; -using Elsa.Telnyx.Client.Services; -using Elsa.Telnyx.Exceptions; -using Elsa.Telnyx.Extensions; -using Elsa.Telnyx.Options; -using Elsa.Workflows; -using Elsa.Workflows.Attributes; -using Elsa.Workflows.UIHints; -using Elsa.Workflows.Models; -using Microsoft.Extensions.Options; - -namespace Elsa.Telnyx.Activities; - -/// -/// Dial a number or SIP URI. -/// -[Activity(Constants.Namespace, "Dial a number or SIP URI.", Kind = ActivityKind.Task)] -public class Dial : CodeActivity -{ - /// - public Dial([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - /// The DID or SIP URI to dial out and bridge to the given call. - /// - [Input(Description = "The DID or SIP URI to dial out and bridge to the given call.")] - public Input To { get; set; } = default!; - - /// - /// The 'from' number to be used as the caller id presented to the destination ('To' number). The number should be in +E164 format. This attribute will default to the 'From' number of the original call if omitted. - /// - [Input(Description = "The 'from' number to be used as the caller id presented to the destination ('To' number). The number should be in +E164 format. This attribute will default to the 'From' number of the original call if omitted.")] - public Input From { get; set; } = default!; - - /// - /// The string to be used as the caller id name (SIP From Display Name) presented to the destination ('To' number). The string should have a maximum of 128 characters, containing only letters, numbers, spaces, and -_~!.+ special characters. If omitted, the display name will be the same as the number in the 'From' field. - /// - [Input(Description = - "The string to be used as the caller id name (SIP From Display Name) presented to the destination ('To' number). The string should have a maximum of 128 characters, containing only letters, numbers, spaces, and -_~!.+ special characters. If omitted, the display name will be the same as the number in the 'From' field.")] - public Input FromDisplayName { get; set; } = default!; - - /// - /// Enables answering machine detection. - /// - [Input( - Description = "Enables answering machine detection.", - UIHint = InputUIHints.DropDown, - Options = new[] { "disabled", "detect", "detect_beep", "detect_words", "greeting_end", "premium" }, - DefaultValue = "disabled")] - public Input AnsweringMachineDetection { get; set; } = new("disabled"); - - /// - /// Start recording automatically after an event. Disabled by default. - /// - [Input(Description = "Start recording automatically after an event. Disabled by default.")] - public Input Record { get; set; } = default!; - - /// - /// Defines the format of the recording ('wav' or 'mp3') when `record` is specified. - /// - [Input( - Description = "Defines the format of the recording ('wav' or 'mp3') when `record` is specified.", - UIHint = InputUIHints.DropDown, - Options = new[] { "wav", "mp3" }, - DefaultValue = "mp3" - )] - public Input RecordFormat { get; set; } = new("mp3"); - - /// - protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) - { - var response = await DialAsync(context); - Result.Set(context, response); - } - - private async Task DialAsync(ActivityExecutionContext context) - { - var telnyxOptions = context.GetRequiredService>().Value; - var callControlAppId = telnyxOptions.CallControlAppId; - - if (callControlAppId == null) - throw new MissingCallControlAppIdException("No Call Control ID configured"); - - var fromNumber = From.GetOrDefault(context); - var clientState = context.CreateCorrelatingClientState(); - - var request = new DialRequest( - callControlAppId, - To.Get(context), - fromNumber, - FromDisplayName.GetOrDefault(context).SanitizeCallerName(), - AnsweringMachineDetection.GetOrDefault(context), - Record: Record.GetOrDefault(context) ? "record-from-answer" : default, - RecordFormat: RecordFormat.GetOrDefault(context) ?? "mp3", - ClientState: clientState - ); - - var telnyxClient = context.GetRequiredService(); - var response = await telnyxClient.Calls.DialAsync(request, context.CancellationToken); - - return response.Data; - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/DialAndWait.cs b/src/modules/Elsa.Telnyx/Activities/DialAndWait.cs deleted file mode 100644 index 751843556..000000000 --- a/src/modules/Elsa.Telnyx/Activities/DialAndWait.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Extensions; -using Elsa.Telnyx.Attributes; -using Elsa.Telnyx.Bookmarks; -using Elsa.Telnyx.Client.Models; -using Elsa.Telnyx.Client.Services; -using Elsa.Telnyx.Exceptions; -using Elsa.Telnyx.Extensions; -using Elsa.Telnyx.Options; -using Elsa.Telnyx.Payloads.Abstractions; -using Elsa.Telnyx.Payloads.Call; -using Elsa.Workflows; -using Elsa.Workflows.Activities.Flowchart.Attributes; -using Elsa.Workflows.Attributes; -using Elsa.Workflows.UIHints; -using Elsa.Workflows.Models; -using Microsoft.Extensions.Options; - -namespace Elsa.Telnyx.Activities; - -/// -/// Dial a number or SIP URI. -/// -[Activity(Constants.Namespace, "Dial a number or SIP URI and wait for an event.", Kind = ActivityKind.Task)] -[FlowNode("Answered", "Hangup")] -[WebhookDriven(WebhookEventTypes.CallAnswered, WebhookEventTypes.CallHangup)] -public class DialAndWait : Activity -{ - /// - public DialAndWait([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - /// The DID or SIP URI to dial out and bridge to the given call. - /// - [Input(Description = "The DID or SIP URI to dial out and bridge to the given call.")] - public Input To { get; set; } = default!; - - /// - /// The 'from' number to be used as the caller id presented to the destination ('To' number). The number should be in +E164 format. This attribute will default to the 'From' number of the original call if omitted. - /// - [Input(Description = "The 'from' number to be used as the caller id presented to the destination ('To' number). The number should be in +E164 format. This attribute will default to the 'From' number of the original call if omitted.")] - public Input From { get; set; } = default!; - - /// - /// The string to be used as the caller id name (SIP From Display Name) presented to the destination ('To' number). The string should have a maximum of 128 characters, containing only letters, numbers, spaces, and -_~!.+ special characters. If omitted, the display name will be the same as the number in the 'From' field. - /// - [Input(Description = - "The string to be used as the caller id name (SIP From Display Name) presented to the destination ('To' number). The string should have a maximum of 128 characters, containing only letters, numbers, spaces, and -_~!.+ special characters. If omitted, the display name will be the same as the number in the 'From' field.")] - public Input FromDisplayName { get; set; } = default!; - - /// - /// Enables answering machine detection. - /// - [Input( - Description = "Enables answering machine detection.", - UIHint = InputUIHints.DropDown, - Options = new[] { "disabled", "detect", "detect_beep", "detect_words", "greeting_end", "premium" }, - DefaultValue = "disabled")] - public Input AnsweringMachineDetection { get; set; } = new("disabled"); - - /// - /// Start recording automatically after an event. Disabled by default. - /// - [Input(Description = "Start recording automatically after an event. Disabled by default.")] - public Input Record { get; set; } = default!; - - /// - /// Defines the format of the recording ('wav' or 'mp3') when `record` is specified. - /// - [Input( - Description = "Defines the format of the recording ('wav' or 'mp3') when `record` is specified.", - UIHint = InputUIHints.DropDown, - Options = new[] { "wav", "mp3" }, - DefaultValue = "mp3" - )] - public Input RecordFormat { get; set; } = new("mp3"); - - /// - protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) - { - var response = await DialAsync(context); - var answeredBookmark = new WebhookEventStimulus(WebhookEventTypes.CallAnswered, response.CallControlId); - var hangupBookmark = new WebhookEventStimulus(WebhookEventTypes.CallHangup, response.CallControlId); - - context.CreateBookmark(answeredBookmark, OnCallAnswered, false); - context.CreateBookmark(hangupBookmark, OnCallHangup, false); - } - - private async ValueTask OnCallAnswered(ActivityExecutionContext context) - { - var payload = context.GetWorkflowInput(); - Result.Set(context, payload); - await context.CompleteActivityWithOutcomesAsync("Answered"); - } - - private async ValueTask OnCallHangup(ActivityExecutionContext context) - { - var payload = context.GetWorkflowInput(); - Result.Set(context, payload); - await context.CompleteActivityWithOutcomesAsync("Hangup"); - } - - private async Task DialAsync(ActivityExecutionContext context) - { - var telnyxOptions = context.GetRequiredService>().Value; - var callControlAppId = telnyxOptions.CallControlAppId; - - if (callControlAppId == null) - throw new MissingCallControlAppIdException("No Call Control ID configured"); - - var fromNumber = From.GetOrDefault(context); - var clientState = context.CreateCorrelatingClientState(); - - var request = new DialRequest( - callControlAppId, - To.Get(context), - fromNumber, - FromDisplayName.GetOrDefault(context).SanitizeCallerName(), - AnsweringMachineDetection.GetOrDefault(context), - Record: Record.GetOrDefault(context) ? "record-from-answer" : default, - RecordFormat: RecordFormat.GetOrDefault(context) ?? "mp3", - ClientState: clientState - ); - - var telnyxClient = context.GetRequiredService(); - var response = await telnyxClient.Calls.DialAsync(request, context.CancellationToken); - - return response.Data; - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/FlowAnswerCall.cs b/src/modules/Elsa.Telnyx/Activities/FlowAnswerCall.cs deleted file mode 100644 index 5851ceb0a..000000000 --- a/src/modules/Elsa.Telnyx/Activities/FlowAnswerCall.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Workflows; -using Elsa.Workflows.Activities.Flowchart.Attributes; -using Elsa.Workflows.Activities.Flowchart.Models; - -namespace Elsa.Telnyx.Activities; - -/// -[FlowNode("Connected", "Disconnected")] -public class FlowAnswerCall : AnswerCallBase -{ - /// - public FlowAnswerCall([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - protected override async ValueTask HandleConnectedAsync(ActivityExecutionContext context) => await context.CompleteActivityAsync(new Outcomes("Connected")); - - /// - protected override async ValueTask HandleDisconnectedAsync(ActivityExecutionContext context) => await context.CompleteActivityAsync(new Outcomes("Disconnected")); -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/FlowBridgeCalls.cs b/src/modules/Elsa.Telnyx/Activities/FlowBridgeCalls.cs deleted file mode 100644 index 24bb3569a..000000000 --- a/src/modules/Elsa.Telnyx/Activities/FlowBridgeCalls.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Workflows; -using Elsa.Workflows.Activities.Flowchart.Attributes; - -namespace Elsa.Telnyx.Activities; - -/// -[FlowNode("Bridged", "Disconnected")] -public class FlowBridgeCalls : BridgeCallsBase -{ - /// - public FlowBridgeCalls([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - protected override ValueTask HandleDisconnectedAsync(ActivityExecutionContext context) => context.CompleteActivityAsync("Disconnected"); - - /// - protected override ValueTask HandleBridgedAsync(ActivityExecutionContext context) => context.CompleteActivityAsync("Bridged"); -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/FlowHangupCall.cs b/src/modules/Elsa.Telnyx/Activities/FlowHangupCall.cs deleted file mode 100644 index 9fb415f8c..000000000 --- a/src/modules/Elsa.Telnyx/Activities/FlowHangupCall.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Workflows; -using Elsa.Workflows.Activities.Flowchart.Attributes; - -namespace Elsa.Telnyx.Activities; - -/// -[FlowNode("Done", "Disconnected")] -public class FlowHangupCall : HangupCallBase -{ - /// - public FlowHangupCall([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - protected override ValueTask HandleDoneAsync(ActivityExecutionContext context) => context.CompleteActivityWithOutcomesAsync("Done"); - - /// - protected override ValueTask HandleDisconnectedAsync(ActivityExecutionContext context) => context.CompleteActivityWithOutcomesAsync("Disconnected"); -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/FlowPlayAudio.cs b/src/modules/Elsa.Telnyx/Activities/FlowPlayAudio.cs deleted file mode 100644 index 30a54f110..000000000 --- a/src/modules/Elsa.Telnyx/Activities/FlowPlayAudio.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Workflows; -using Elsa.Workflows.Activities.Flowchart.Attributes; - -namespace Elsa.Telnyx.Activities; - -/// -[FlowNode("Playback started", "Disconnected")] -public class FlowPlayAudio : PlayAudioBase -{ - /// - public FlowPlayAudio([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - protected override ValueTask HandlePlaybackStartedAsync(ActivityExecutionContext context) => context.CompleteActivityWithOutcomesAsync("Playback started"); - - /// - protected override ValueTask HandleDisconnectedAsync(ActivityExecutionContext context) => context.CompleteActivityWithOutcomesAsync("Disconnected"); -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/FlowSpeakText.cs b/src/modules/Elsa.Telnyx/Activities/FlowSpeakText.cs deleted file mode 100644 index eeddbc958..000000000 --- a/src/modules/Elsa.Telnyx/Activities/FlowSpeakText.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Workflows; -using Elsa.Workflows.Activities.Flowchart.Attributes; - -namespace Elsa.Telnyx.Activities; - -/// -[FlowNode("Done", "Finished speaking", "Disconnected")] -public class FlowSpeakText : SpeakTextBase -{ - /// - public FlowSpeakText([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - protected override async ValueTask HandleDisconnected(ActivityExecutionContext context) => await context.CompleteActivityWithOutcomesAsync("Disconnected", "Done"); - - /// - protected override async ValueTask HandleSpeakingHasFinished(ActivityExecutionContext context) => await context.CompleteActivityWithOutcomesAsync("Finished speaking", "Done"); -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/FlowStartRecording.cs b/src/modules/Elsa.Telnyx/Activities/FlowStartRecording.cs deleted file mode 100644 index 7ca25d52e..000000000 --- a/src/modules/Elsa.Telnyx/Activities/FlowStartRecording.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Workflows; -using Elsa.Workflows.Activities.Flowchart.Attributes; - -namespace Elsa.Telnyx.Activities; - -/// -[FlowNode("Recording finished", "Disconnected")] -public class FlowStartRecording : StartRecordingBase -{ - /// - public FlowStartRecording([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - protected override ValueTask HandleDisconnectedAsync(ActivityExecutionContext context) => context.CompleteActivityWithOutcomesAsync("Disconnected"); - - /// - protected override ValueTask HandleCallRecordingSavedAsync(ActivityExecutionContext context) => context.CompleteActivityWithOutcomesAsync("Recording finished"); -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/FlowStopAudioPlayback.cs b/src/modules/Elsa.Telnyx/Activities/FlowStopAudioPlayback.cs deleted file mode 100644 index cb946d2a0..000000000 --- a/src/modules/Elsa.Telnyx/Activities/FlowStopAudioPlayback.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Workflows; -using Elsa.Workflows.Activities.Flowchart.Attributes; - -namespace Elsa.Telnyx.Activities; - -/// -[FlowNode("Done", "Disconnected")] -public class FlowStopAudioPlayback : StopAudioPlaybackBase -{ - /// - public FlowStopAudioPlayback([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - protected override ValueTask HandleDoneAsync(ActivityExecutionContext context) => context.CompleteActivityWithOutcomesAsync("Done"); - - /// - protected override ValueTask HandleDisconnectedAsync(ActivityExecutionContext context) => context.CompleteActivityWithOutcomesAsync("Disconnected"); -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/GatherUsingAudio.cs b/src/modules/Elsa.Telnyx/Activities/GatherUsingAudio.cs deleted file mode 100644 index 082ac0dcd..000000000 --- a/src/modules/Elsa.Telnyx/Activities/GatherUsingAudio.cs +++ /dev/null @@ -1,158 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Extensions; -using Elsa.Telnyx.Attributes; -using Elsa.Telnyx.Bookmarks; -using Elsa.Telnyx.Client.Models; -using Elsa.Telnyx.Client.Services; -using Elsa.Telnyx.Extensions; -using Elsa.Telnyx.Payloads.Call; -using Elsa.Workflows; -using Elsa.Workflows.Activities.Flowchart.Attributes; -using Elsa.Workflows.Attributes; -using Elsa.Workflows.Models; -using Elsa.Workflows.Runtime; -using Refit; - -namespace Elsa.Telnyx.Activities; - -/// -/// Play an audio file on the call until the required DTMF signals are gathered to build interactive menus. -/// -[Activity(Constants.Namespace, "Play an audio file on the call until the required DTMF signals are gathered to build interactive menus.", Kind = ActivityKind.Task)] -[FlowNode("Valid input", "Invalid input", "Disconnected")] -[WebhookDriven(WebhookEventTypes.CallGatherEnded)] -public class GatherUsingAudio : Activity, IBookmarksPersistedHandler -{ - /// - public GatherUsingAudio([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - /// The call control ID of the call from which to gather input. Leave empty to use the ambient call control ID, if there is any. - /// - [Input(DisplayName = "Call Control ID", Description = "The call control ID of the call from which to gather input. Leave empty to use the ambient call control ID, if there is any.", Category = "Advanced")] - public Input CallControlId { get; set; } = default!; - - /// - /// The URL of a file to be played back at the beginning of each prompt. The URL can point to either a WAV or MP3 file. - /// - [Input( - DisplayName = "Audio URL", - Description = "The URL of a file to be played back at the beginning of each prompt. The URL can point to either a WAV or MP3 file." - )] - public Input? AudioUrl { get; set; } = default!; - - /// - /// The number of milliseconds to wait for input between digits. - /// - [Input( - DisplayName = "Inter Digit Timeout", - Description = "The number of milliseconds to wait for input between digits.", - Category = "Advanced", - DefaultValue = 5000 - )] - public Input? InterDigitTimeoutMillis { get; set; } = new(5000); - - /// - /// The URL of a file to play when digits don't match the Valid Digits setting or the number of digits is not between Min and Max. The URL can point to either a WAV or MP3 file. - /// - [Input( - DisplayName = "Invalid Audio Url", - Description = "The URL of a file to play when digits don't match the Valid Digits setting or the number of digits is not between Min and Max. The URL can point to either a WAV or MP3 file." - )] - public Input? InvalidAudioUrl { get; set; } - - /// - /// A list of all digits accepted as valid. - /// - [Input( - Description = "A list of all digits accepted as valid.", - Category = "Advanced", - DefaultValue = "0123456789#*" - )] - public Input? ValidDigits { get; set; } = new("0123456789#*"); - - /// - /// The minimum number of digits to fetch. This parameter has a minimum value of 1. - /// - [Input(Description = "The minimum number of digits to fetch. This parameter has a minimum value of 1.", DefaultValue = 1)] - public Input? MinimumDigits { get; set; } = new(1); - - /// - /// The maximum number of digits to fetch. This parameter has a maximum value of 128. - /// - [Input(Description = "The maximum number of digits to fetch. This parameter has a maximum value of 128.", DefaultValue = 128)] - public Input? MaximumDigits { get; set; } = new(128); - - /// - /// The maximum number of times the file should be played if there is no input from the user on the call. - /// - [Input(Description = "The maximum number of times the file should be played if there is no input from the user on the call.", DefaultValue = 3)] - public Input? MaximumTries { get; set; } = new(3); - - /// - /// The digit used to terminate input if fewer than `maximum_digits` digits have been gathered. - /// - [Input(Description = "The digit used to terminate input if fewer than `maximum_digits` digits have been gathered.", DefaultValue = "#")] - public Input? TerminatingDigit { get; set; } = new("#"); - - /// - /// The number of milliseconds to wait for a DTMF response after file playback ends before a replaying the sound file. - /// - [Input( - DisplayName = "Timeout", - Description = "The number of milliseconds to wait for a DTMF response after file playback ends before a replaying the sound file.", - Category = "Advanced", - DefaultValue = 60000 - )] - public Input? TimeoutMillis { get; set; } = new(60000); - - /// - /// Calls out to Telnyx to actually begin gathering input. - /// - public async ValueTask BookmarksPersistedAsync(ActivityExecutionContext context) - { - var request = new GatherUsingAudioRequest( - AudioUrl.Get(context) ?? throw new Exception("AudioUrl is required."), - context.CreateCorrelatingClientState(), - default, - InterDigitTimeoutMillis.Get(context), - InvalidAudioUrl.Get(context), - MaximumDigits.Get(context), - MaximumTries.Get(context), - MinimumDigits.Get(context), - TerminatingDigit.Get(context).EmptyToNull(), - TimeoutMillis.Get(context), - ValidDigits.Get(context).EmptyToNull() - ); - - var callControlId = CallControlId.Get(context); - var telnyxClient = context.GetRequiredService(); - - try - { - await telnyxClient.Calls.GatherUsingAudioAsync(callControlId, request, context.CancellationToken); - } - catch (ApiException e) - { - if (!await e.CallIsNoLongerActiveAsync()) throw; - await context.CompleteActivityWithOutcomesAsync("Disconnected"); - } - } - - /// - protected override void Execute(ActivityExecutionContext context) - { - var callControlId = CallControlId.Get(context); - context.CreateBookmark(new WebhookEventStimulus(WebhookEventTypes.CallGatherEnded, callControlId), ResumeAsync); - } - - private async ValueTask ResumeAsync(ActivityExecutionContext context) - { - var payload = context.GetWorkflowInput(); - var outcome = payload.Status == "valid" ? "Valid input" : "Invalid input"; - context.Set(Result, payload); - await context.CompleteActivityWithOutcomesAsync(outcome); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/GatherUsingSpeak.cs b/src/modules/Elsa.Telnyx/Activities/GatherUsingSpeak.cs deleted file mode 100644 index eaea2c4ca..000000000 --- a/src/modules/Elsa.Telnyx/Activities/GatherUsingSpeak.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Extensions; -using Elsa.Telnyx.Attributes; -using Elsa.Telnyx.Bookmarks; -using Elsa.Telnyx.Client.Models; -using Elsa.Telnyx.Client.Services; -using Elsa.Telnyx.Extensions; -using Elsa.Telnyx.Payloads.Call; -using Elsa.Workflows; -using Elsa.Workflows.Activities.Flowchart.Attributes; -using Elsa.Workflows.Attributes; -using Elsa.Workflows.UIHints; -using Elsa.Workflows.Models; -using Refit; - -namespace Elsa.Telnyx.Activities; - -/// -/// Convert text to speech and play it on the call until the required DTMF signals are gathered to build interactive menus. -/// -[Activity(Constants.Namespace, "Convert text to speech and play it on the call until the required DTMF signals are gathered to build interactive menus.", Kind = ActivityKind.Task)] -[FlowNode("Valid input", "Invalid input", "Disconnected")] -[WebhookDriven(WebhookEventTypes.CallGatherEnded)] -public class GatherUsingSpeak : Activity -{ - /// - public GatherUsingSpeak([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - /// The call control ID of the call from which to gather input. Leave empty to use the ambient call control ID, if there is any. - /// - [Input(DisplayName = "Call Control ID", Description = "The call control ID of the call from which to gather input. Leave empty to use the ambient call control ID, if there is any.", Category = "Advanced")] - public Input CallControlId { get; set; } = default!; - - /// - /// The language you want spoken. - /// - [Input( - Description = "The language you want spoken.", - UIHint = InputUIHints.DropDown, - Options = new[] { "en-US", "en-AU", "nl-NL", "es-ES", "ru-RU" }, - DefaultValue = "en-US" - )] - public Input Language { get; set; } = new("en-US"); - - /// - /// The gender of the voice used to speak back the text. - /// - [Input( - Description = "The gender of the voice used to speak back the text.", - UIHint = InputUIHints.DropDown, - Options = new[] { "female", "male" }, - DefaultValue = "female" - )] - public Input Voice { get; set; } = new("female"); - - /// - /// The text or SSML to be converted into speech. There is a 5,000 character limit. - /// - [Input( - Description = "The text or SSML to be converted into speech. There is a 5,000 character limit.", - UIHint = InputUIHints.MultiLine - )] - public Input Payload { get; set; } = default!; - - /// - /// The type of the provided payload. The payload can either be plain text, or Speech Synthesis Markup Language (SSML). - /// - [Input( - Description = "The type of the provided payload. The payload can either be plain text, or Speech Synthesis Markup Language (SSML).", - UIHint = InputUIHints.DropDown, - Options = new[] { "", "text", "ssml" } - )] - public Input? PayloadType { get; set; } - - /// - /// "This parameter impacts speech quality, language options and payload types. When using `basic`, only the `en-US` language and payload type `text` are allowed." - /// - [Input( - Description = "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 = InputUIHints.DropDown, - Options = new[] { "", "basic", "premium" }, - Category = "Advanced" - )] - public Input? ServiceLevel { get; set; } - - /// - /// The number of milliseconds to wait for input between digits. - /// - [Input( - DisplayName = "Inter Digit Timeout", - Description = "The number of milliseconds to wait for input between digits.", - Category = "Advanced", - DefaultValue = 5000 - )] - public Input? InterDigitTimeoutMillis { get; set; } = new(5000); - - /// - /// A list of all digits accepted as valid. - /// - [Input( - Description = "A list of all digits accepted as valid.", - Category = "Advanced", - DefaultValue = "0123456789#*" - )] - public Input? ValidDigits { get; set; } = new("0123456789#*"); - - /// - /// The minimum number of digits to fetch. This parameter has a minimum value of 1. - /// - [Input(Description = "The minimum number of digits to fetch. This parameter has a minimum value of 1.", DefaultValue = 1)] - public Input? MinimumDigits { get; set; } = new(1); - - /// - /// The maximum number of digits to fetch. This parameter has a maximum value of 128. - /// - [Input(Description = "The maximum number of digits to fetch. This parameter has a maximum value of 128.", DefaultValue = 128)] - public Input? MaximumDigits { get; set; } = new(128); - - /// - /// The maximum number of times the file should be played if there is no input from the user on the call. - /// - [Input(Description = "The maximum number of times the file should be played if there is no input from the user on the call.", DefaultValue = 3)] - public Input? MaximumTries { get; set; } = new(3); - - /// - /// The digit used to terminate input if fewer than `maximum_digits` digits have been gathered. - /// - [Input(Description = "The digit used to terminate input if fewer than `maximum_digits` digits have been gathered.", DefaultValue = "#")] - public Input? TerminatingDigit { get; set; } = new("#"); - - /// - /// The number of milliseconds to wait for a DTMF response after file playback ends before a replaying the sound file. - /// - [Input( - DisplayName = "Timeout", - Description = "The number of milliseconds to wait for a DTMF response after file playback ends before a replaying the sound file.", - Category = "Advanced", - DefaultValue = 60000 - )] - public Input? TimeoutMillis { get; set; } = new(60000); - - /// - protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) - { - var callControlId = CallControlId.Get(context); - - var request = new GatherUsingSpeakRequest( - Language.Get(context) ?? throw new Exception("Language is required."), - Voice.Get(context) ?? throw new Exception("Voice is required."), - Payload.Get(context) ?? throw new Exception("Payload is required."), - PayloadType.GetOrDefault(context), - ServiceLevel.GetOrDefault(context), - InterDigitTimeoutMillis.GetOrDefault(context), - MaximumDigits.GetOrDefault(context), - MaximumTries.GetOrDefault(context), - MinimumDigits.GetOrDefault(context), - TerminatingDigit.GetOrDefault(context).EmptyToNull(), - TimeoutMillis.GetOrDefault(context), - ValidDigits.GetOrDefault(context).EmptyToNull(), - context.CreateCorrelatingClientState(context.Id) - ); - - var telnyxClient = context.GetRequiredService(); - - try - { - // Send the request to Telnyx. - await telnyxClient.Calls.GatherUsingSpeakAsync(callControlId, request, context.CancellationToken); - - // Create a bookmark so we can resume this activity when the call.gather.ended webhook comes back. - context.CreateBookmark(new WebhookEventStimulus(WebhookEventTypes.CallGatherEnded, callControlId), ResumeAsync, true); - } - catch (ApiException e) - { - if (!await e.CallIsNoLongerActiveAsync()) throw; - await context.CompleteActivityWithOutcomesAsync("Disconnected"); - } - } - - private async ValueTask ResumeAsync(ActivityExecutionContext context) - { - var payload = context.GetWorkflowInput(); - var outcome = payload.Status == "valid" ? "Valid input" : "Invalid input"; - context.Set(Result, payload); - await context.CompleteActivityWithOutcomesAsync(outcome); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/GetCallStatus.cs b/src/modules/Elsa.Telnyx/Activities/GetCallStatus.cs deleted file mode 100644 index 4f0aa5c9e..000000000 --- a/src/modules/Elsa.Telnyx/Activities/GetCallStatus.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Extensions; -using Elsa.Telnyx.Client.Services; -using Elsa.Workflows; -using Elsa.Workflows.Activities.Flowchart.Attributes; -using Elsa.Workflows.Attributes; -using Elsa.Workflows.Models; - -namespace Elsa.Telnyx.Activities; - -/// -[FlowNode("Alive", "Dead", "Done")] -[Activity(Constants.Namespace, "Get the status of a call.", Kind = ActivityKind.Task)] -public class GetCallStatus : Activity -{ - /// - public GetCallStatus([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - /// Unique identifier and token for controlling the call. - /// - [Input( - DisplayName = "Call Control ID", - Description = "Unique identifier and token for controlling the call.", - Category = "Advanced" - )] - public Input CallControlId { get; set; } = default!; - - /// - protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) - { - var client = context.GetRequiredService(); - var callControlId = CallControlId.Get(context); - var response = await client.Calls.GetStatusAsync(callControlId, context.CancellationToken); - var isAlive = response.Data.IsAlive; - var outcome = isAlive ? "Alive" : "Dead"; - - Result.Set(context, isAlive); - - await context.CompleteActivityWithOutcomesAsync(outcome, "Done"); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/HangupCall.cs b/src/modules/Elsa.Telnyx/Activities/HangupCall.cs deleted file mode 100644 index c378d9d2a..000000000 --- a/src/modules/Elsa.Telnyx/Activities/HangupCall.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Workflows; -using Elsa.Workflows.Attributes; -using JetBrains.Annotations; - -namespace Elsa.Telnyx.Activities; - -/// -[PublicAPI] -public class HangupCall : HangupCallBase -{ - /// - public HangupCall([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - /// The to execute when the call was no longer active. - /// - [Port] public IActivity? Disconnected { get; set; } - - /// - protected override async ValueTask HandleDoneAsync(ActivityExecutionContext context) => await context.CompleteActivityAsync(OnCompletedAsync); - - /// - protected override async ValueTask HandleDisconnectedAsync(ActivityExecutionContext context) => await context.ScheduleActivityAsync(Disconnected, OnCompletedAsync); - - /// - /// Executed when any child activity completed. - /// - private async ValueTask OnCompletedAsync(ActivityCompletedContext context) => await context.TargetContext.CompleteActivityAsync(); -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/HangupCallBase.cs b/src/modules/Elsa.Telnyx/Activities/HangupCallBase.cs deleted file mode 100644 index dd2cb998c..000000000 --- a/src/modules/Elsa.Telnyx/Activities/HangupCallBase.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Elsa.Extensions; -using Elsa.Telnyx.Client.Models; -using Elsa.Telnyx.Client.Services; -using Elsa.Telnyx.Extensions; -using Elsa.Workflows; -using Elsa.Workflows.Attributes; -using Elsa.Workflows.Models; -using Refit; - -namespace Elsa.Telnyx.Activities; - -/// -/// Hang up the call. -/// -[Activity(Constants.Namespace, "Hang up the call.", Kind = ActivityKind.Task)] -public abstract class HangupCallBase : Activity -{ - /// - protected HangupCallBase(string? source = default, int? line = default) : base(source, line) - { - } - - /// - /// Unique identifier and token for controlling the call. - /// - [Input(DisplayName = "Call Control ID", Description = "Unique identifier and token for controlling the call.", Category = "Advanced")] - public Input CallControlId { get; set; } = default!; - - /// - protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) - { - var callControlId = CallControlId.Get(context); - var request = new HangupCallRequest(ClientState: context.CreateCorrelatingClientState()); - var telnyxClient = context.GetRequiredService(); - - try - { - await telnyxClient.Calls.HangupCallAsync(callControlId, request, context.CancellationToken); - await HandleDoneAsync(context); - } - catch (ApiException e) - { - if (!await e.CallIsNoLongerActiveAsync()) throw; - await HandleDisconnectedAsync(context); - } - } - - /// - /// Executed when the call was hangup. - /// - protected abstract ValueTask HandleDoneAsync(ActivityExecutionContext context); - - /// - /// Executed when the call was no longer active. - /// - protected abstract ValueTask HandleDisconnectedAsync(ActivityExecutionContext context); -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/IncomingCall.cs b/src/modules/Elsa.Telnyx/Activities/IncomingCall.cs deleted file mode 100644 index 82df2aa0c..000000000 --- a/src/modules/Elsa.Telnyx/Activities/IncomingCall.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Expressions.Models; -using Elsa.Extensions; -using Elsa.Telnyx.Bookmarks; -using Elsa.Telnyx.Helpers; -using Elsa.Telnyx.Models; -using Elsa.Telnyx.Payloads.Call; -using Elsa.Workflows; -using Elsa.Workflows.Attributes; -using Elsa.Workflows.UIHints; -using Elsa.Workflows.Models; - -namespace Elsa.Telnyx.Activities; - -/// -/// Triggered when an inbound phone call is received for any of the specified source or destination phone numbers. -/// -[Activity( - "Telnyx", - "Telnyx", - "Triggered when an inbound phone call is received for any of the specified source or destination phone numbers.", - Kind = ActivityKind.Trigger)] -public class IncomingCall : Trigger -{ - /// - public IncomingCall([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - /// A list of destination numbers to respond to. - /// - [Input(Description = "A list of destination numbers to respond to.", UIHint = InputUIHints.MultiText)] - public Input> To { get; set; } = default!; - - /// - /// A list of source numbers to respond to. - /// - [Input(Description = "A list of source numbers to respond to.", UIHint = InputUIHints.MultiText)] - public Input> From { get; set; } = default!; - - /// - /// Match any inbound calls. - /// - [Input(Description = "Match any inbound calls.")] - public Input CatchAll { get; set; } = default!; - - /// - protected override IEnumerable GetTriggerPayloads(TriggerIndexingContext context) => GetBookmarkPayloads(context.ExpressionExecutionContext); - - /// - protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) - { - if (context.IsTriggerOfWorkflow()) - { - await ResumeAsync(context); - } - else - { - var bookmarkPayloads = GetBookmarkPayloads(context.ExpressionExecutionContext); - foreach (var bookmarkPayload in bookmarkPayloads) context.CreateBookmark(bookmarkPayload, ResumeAsync); - } - } - - private async ValueTask ResumeAsync(ActivityExecutionContext context) - { - var webhookModel = context.GetWorkflowInput(WebhookSerializerOptions.Create()); - var callInitiatedPayload = (CallInitiatedPayload)webhookModel.Data.Payload; - - // Store webhook payload as output. - Result.Set(context, callInitiatedPayload); - - await context.CompleteActivityAsync(); - } - - private IEnumerable GetBookmarkPayloads(ExpressionExecutionContext context) - { - var from = context.Get(From) ?? ArraySegment.Empty; - var to = context.Get(To) ?? ArraySegment.Empty; - var catchAll = context.Get(CatchAll); - - foreach (var phoneNumber in from) yield return new IncomingCallFromStimulus(phoneNumber); - foreach (var phoneNumber in to) yield return new IncomingCallToStimulus(phoneNumber); - - if (catchAll) - yield return new IncomingCallCatchAllStimulus(); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/LookupNumber.cs b/src/modules/Elsa.Telnyx/Activities/LookupNumber.cs deleted file mode 100644 index 86b6c3254..000000000 --- a/src/modules/Elsa.Telnyx/Activities/LookupNumber.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Extensions; -using Elsa.Telnyx.Client.Models; -using Elsa.Telnyx.Client.Services; -using Elsa.Workflows; -using Elsa.Workflows.Attributes; -using Elsa.Workflows.UIHints; -using Elsa.Workflows.Models; - -namespace Elsa.Telnyx.Activities; - -/// -/// Returns information about the provided phone number. -/// -[Activity(Constants.Namespace, "Returns information about the provided phone number.", Kind = ActivityKind.Task)] -public class LookupNumber : CodeActivity -{ - /// - public LookupNumber([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - /// The phone number to be looked up. - /// - [Input(Description = "The phone number to be looked up.")] - public Input PhoneNumber { get; set; } = default!; - - /// - /// The types of number lookups to be performed. - /// - [Input( - Description = "The types of number lookups to be performed.", - UIHint = InputUIHints.CheckList, - Options = new[] { "carrier", "caller-name" } - )] - public Input> Types { get; set; } = new(new List()); - - /// - protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) - { - var telnyxClient = context.GetRequiredService(); - var phoneNumber = PhoneNumber.Get(context) ?? throw new Exception("PhoneNumber is required."); - var types = Types.Get(context); - var response = await telnyxClient.NumberLookup.NumberLookupAsync(phoneNumber, types, context.CancellationToken); - context.Set(Result, response.Data); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/PlayAudio.cs b/src/modules/Elsa.Telnyx/Activities/PlayAudio.cs deleted file mode 100644 index b5131eead..000000000 --- a/src/modules/Elsa.Telnyx/Activities/PlayAudio.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Workflows; -using Elsa.Workflows.Attributes; -using JetBrains.Annotations; - -namespace Elsa.Telnyx.Activities; - -/// -[PublicAPI] -public class PlayAudio : PlayAudioBase -{ - /// - public PlayAudio([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - /// The to execute when the call was no longer active. - /// - [Port] - public IActivity? Disconnected { get; set; } - - /// - protected override async ValueTask HandlePlaybackStartedAsync(ActivityExecutionContext context) => await context.CompleteActivityAsync(); - - /// - protected override async ValueTask HandleDisconnectedAsync(ActivityExecutionContext context) => await context.ScheduleActivityAsync(Disconnected, OnCompletedAsync); - - private async ValueTask OnCompletedAsync(ActivityCompletedContext context) => await context.TargetContext.CompleteActivityAsync(); -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/PlayAudioBase.cs b/src/modules/Elsa.Telnyx/Activities/PlayAudioBase.cs deleted file mode 100644 index eba1ffa8b..000000000 --- a/src/modules/Elsa.Telnyx/Activities/PlayAudioBase.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Extensions; -using Elsa.Telnyx.Attributes; -using Elsa.Telnyx.Bookmarks; -using Elsa.Telnyx.Client.Models; -using Elsa.Telnyx.Client.Services; -using Elsa.Telnyx.Extensions; -using Elsa.Workflows; -using Elsa.Workflows.Activities.Flowchart.Attributes; -using Elsa.Workflows.Attributes; -using Elsa.Workflows.UIHints; -using Elsa.Workflows.Models; -using Refit; - -namespace Elsa.Telnyx.Activities; - -/// -/// Play an audio file on the call. -/// -[Activity(Constants.Namespace, "Play an audio file on the call.", Kind = ActivityKind.Task)] -[FlowNode("Playback started", "Disconnected")] -[WebhookDriven(WebhookEventTypes.CallPlaybackStarted)] -public abstract class PlayAudioBase : Activity -{ - /// - protected PlayAudioBase([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - /// Unique identifier and token for controlling the call. - /// - [Input( - DisplayName = "Call Control ID", - Description = "Unique identifier and token for controlling the call.", - Category = "Advanced" - )] - public Input CallControlId { get; set; } = default!; - - /// - /// The URL of a file to be played back at the beginning of each prompt. The URL can point to either a WAV or MP3 file. - /// - [Input( - DisplayName = "Audio URL", - Description = "The URL of a file to be played back at the beginning of each prompt. The URL can point to either a WAV or MP3 file." - )] - public Input AudioUrl { get; set; } = default!; - - /// - /// The number of times the audio file should be played. If supplied, the value must be an integer between 1 and 100, or the special string 'infinity' for an endless loop. - /// - [Input( - Description = "The number of times the audio file should be played. If supplied, the value must be an integer between 1 and 100, or the special string 'infinity' for an endless loop.", - DefaultValue = "1", - Category = "Advanced" - )] - public Input Loop { get; set; } = new("1"); - - /// - /// When enabled, audio will be mixed on top of any other audio that is actively being played back. Note that `overlay: true` will only work if there is another audio file already being played on the call. - /// - [Input( - Description = "When enabled, audio will be mixed on top of any other audio that is actively being played back. Note that `overlay: true` will only work if there is another audio file already being played on the call.", - DefaultValue = false, - Category = "Advanced" - )] - public Input Overlay { get; set; } = new(false); - - /// - /// Specifies the leg or legs on which audio will be played. If supplied, the value must be either 'self', 'opposite' or 'both'. - /// - [Input( - Description = "Specifies the leg or legs on which audio will be played. If supplied, the value must be either 'self', 'opposite' or 'both'.", - UIHint = InputUIHints.DropDown, - Options = new[] { "", "self", "opposite", "both" }, - Category = "Advanced" - )] - public Input TargetLegs { get; set; } = default!; - - /// - protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) - { - var loop = Loop.GetOrDefault(context); - - var request = new PlayAudioRequest( - AudioUrl.Get(context), - Overlay.GetOrDefault(context), - string.IsNullOrWhiteSpace(loop) ? null : loop == "infinity" ? "infinity" : int.Parse(loop), - TargetLegs.GetOrDefault(context).EmptyToNull(), - ClientState: context.CreateCorrelatingClientState(context.Id) - ); - - var callControlId = CallControlId.Get(context); - var telnyxClient = context.GetRequiredService(); - - try - { - await telnyxClient.Calls.PlayAudioAsync(callControlId, request, context.CancellationToken); - } - catch (ApiException e) - { - if (!await e.CallIsNoLongerActiveAsync()) throw; - await HandleDisconnectedAsync(context); - } - - context.CreateBookmark(new WebhookEventStimulus(WebhookEventTypes.CallPlaybackStarted, callControlId), ResumeAsync, true); - } - - /// - /// Called when playback has started. - /// - protected abstract ValueTask HandlePlaybackStartedAsync(ActivityExecutionContext context); - - - /// - /// Called when the call was no longer active. - /// - protected abstract ValueTask HandleDisconnectedAsync(ActivityExecutionContext context); - - private async ValueTask ResumeAsync(ActivityExecutionContext context) => await HandlePlaybackStartedAsync(context); -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/SpeakText.cs b/src/modules/Elsa.Telnyx/Activities/SpeakText.cs deleted file mode 100644 index 0b2ad1f83..000000000 --- a/src/modules/Elsa.Telnyx/Activities/SpeakText.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Workflows; -using Elsa.Workflows.Attributes; -using JetBrains.Annotations; - -namespace Elsa.Telnyx.Activities; - -/// -[PublicAPI] -public class SpeakText : SpeakTextBase -{ - /// - public SpeakText([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - /// The to execute when the call was no longer active. - /// - [Port] - public IActivity? Disconnected { get; set; } - - /// - /// The to execute when speaking has finished. - /// - [Port] - public IActivity? FinishedSpeaking { get; set; } - - /// - protected override async ValueTask HandleDisconnected(ActivityExecutionContext context) => await context.ScheduleActivityAsync(Disconnected); - - /// - protected override async ValueTask HandleSpeakingHasFinished(ActivityExecutionContext context) => await context.ScheduleActivityAsync(FinishedSpeaking); -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/SpeakTextBase.cs b/src/modules/Elsa.Telnyx/Activities/SpeakTextBase.cs deleted file mode 100644 index e990b78a2..000000000 --- a/src/modules/Elsa.Telnyx/Activities/SpeakTextBase.cs +++ /dev/null @@ -1,128 +0,0 @@ -using Elsa.Extensions; -using Elsa.Telnyx.Attributes; -using Elsa.Telnyx.Bookmarks; -using Elsa.Telnyx.Client.Models; -using Elsa.Telnyx.Client.Services; -using Elsa.Telnyx.Extensions; -using Elsa.Workflows; -using Elsa.Workflows.Attributes; -using Elsa.Workflows.UIHints; -using Elsa.Workflows.Models; -using Refit; - -namespace Elsa.Telnyx.Activities; - -/// -/// Convert text to speech and play it back on the call. -/// -[Activity(Constants.Namespace, "Convert text to speech and play it back on the call.", Kind = ActivityKind.Task)] -[WebhookDriven(WebhookEventTypes.CallSpeakEnded)] -public abstract class SpeakTextBase : Activity -{ - /// - protected SpeakTextBase(string? source = default, int? line = default) : base(source, line) - { - } - - /// - /// Unique identifier and token for controlling the call. - /// - [Input( - DisplayName = "Call Control ID", - Description = "Unique identifier and token for controlling the call.", - Category = "Advanced" - )] - public Input CallControlId { get; set; } = default!; - - /// - /// The language you want spoken. - /// - [Input( - Description = "The language you want spoken.", - UIHint = InputUIHints.DropDown, - Options = new[] { "en-US", "en-AU", "nl-NL", "es-ES", "ru-RU" }, - DefaultValue = "en-US" - )] - public Input Language { get; set; } = new("en-US"); - - /// - /// The gender of the voice used to speak back the text. - /// - [Input( - Description = "The gender of the voice used to speak back the text.", - UIHint = InputUIHints.DropDown, - Options = new[] { "female", "male" }, - DefaultValue = "female" - )] - public Input Voice { get; set; } = new("female"); - - /// - /// The text or SSML to be converted into speech. There is a 5,000 character limit. - /// - [Input( - Description = "The text or SSML to be converted into speech. There is a 5,000 character limit.", - UIHint = InputUIHints.MultiLine - )] - public Input Payload { get; set; } = default!; - - /// - /// The type of the provided payload. The payload can either be plain text, or Speech Synthesis Markup Language (SSML). - /// - [Input( - Description = "The type of the provided payload. The payload can either be plain text, or Speech Synthesis Markup Language (SSML).", - UIHint = InputUIHints.DropDown, - Options = new[] { "", "text", "ssml" } - )] - public Input? PayloadType { get; set; } - - /// - /// This parameter impacts speech quality, language options and payload types. When using `basic`, only the `en-US` language and payload type `text` are allowed. - /// - [Input( - Description = "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 = InputUIHints.DropDown, - Options = new[] { "", "basic", "premium" }, - Category = "Advanced" - )] - public Input ServiceLevel { get; set; } = default!; - - /// - protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) - { - var request = new SpeakTextRequest( - Language.GetOrDefault(context) ?? "en-US", - Voice.GetOrDefault(context) ?? "female", - Payload.Get(context), - PayloadType.GetOrDefault(context).EmptyToNull(), - ServiceLevel.GetOrDefault(context).EmptyToNull(), - ClientState: context.CreateCorrelatingClientState(context.Id) - ); - - var callControlId = CallControlId.Get(context); - var telnyxClient = context.GetRequiredService(); - - try - { - // Send the request to Telnyx. - await telnyxClient.Calls.SpeakTextAsync(callControlId, request, context.CancellationToken); - - // Create bookmark to resume the workflow when speaking has finished. - context.CreateBookmark(new WebhookEventStimulus(WebhookEventTypes.CallSpeakEnded, callControlId), HandleSpeakingHasFinished, true); - } - catch (ApiException e) - { - if (!await e.CallIsNoLongerActiveAsync()) throw; - await HandleDisconnected(context); - } - } - - /// - /// Called when the call was no longer active. - /// - protected abstract ValueTask HandleDisconnected(ActivityExecutionContext context); - - /// - /// Called when speaking has finished. - /// - protected abstract ValueTask HandleSpeakingHasFinished(ActivityExecutionContext context); -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/StartRecording.cs b/src/modules/Elsa.Telnyx/Activities/StartRecording.cs deleted file mode 100644 index c61efd7a0..000000000 --- a/src/modules/Elsa.Telnyx/Activities/StartRecording.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Workflows; -using Elsa.Workflows.Attributes; -using JetBrains.Annotations; - -namespace Elsa.Telnyx.Activities; - -/// -[PublicAPI] -public class StartRecording : StartRecordingBase -{ - /// - public StartRecording([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - /// The to execute when recording has finished. - /// - [Port] public IActivity? RecordingFinished { get; set; } - - /// - /// The to executed when the call was no longer active. - /// - [Port] public IActivity? Disconnected { get; set; } - - /// - protected override async ValueTask HandleCallRecordingSavedAsync(ActivityExecutionContext context) => await context.ScheduleActivityAsync(RecordingFinished, OnCompletedAsync); - - /// - protected override async ValueTask HandleDisconnectedAsync(ActivityExecutionContext context) => await context.ScheduleActivityAsync(Disconnected, OnCompletedAsync); - - private async ValueTask OnCompletedAsync(ActivityCompletedContext context) => await context.TargetContext.CompleteActivityAsync(); -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/StartRecordingBase.cs b/src/modules/Elsa.Telnyx/Activities/StartRecordingBase.cs deleted file mode 100644 index c6cef5e64..000000000 --- a/src/modules/Elsa.Telnyx/Activities/StartRecordingBase.cs +++ /dev/null @@ -1,109 +0,0 @@ -using Elsa.Extensions; -using Elsa.Telnyx.Attributes; -using Elsa.Telnyx.Bookmarks; -using Elsa.Telnyx.Client.Models; -using Elsa.Telnyx.Client.Services; -using Elsa.Telnyx.Extensions; -using Elsa.Telnyx.Payloads.Call; -using Elsa.Workflows; -using Elsa.Workflows.Attributes; -using Elsa.Workflows.UIHints; -using Elsa.Workflows.Models; -using Refit; - -namespace Elsa.Telnyx.Activities; - -/// -/// Start recording the call. -/// -[Activity(Constants.Namespace, "Start recording the call.", Kind = ActivityKind.Task)] -[WebhookDriven(WebhookEventTypes.CallRecordingSaved)] -public abstract class StartRecordingBase : Activity -{ - /// - protected StartRecordingBase(string? source = default, int? line = default) : base(source, line) - { - } - - /// - /// Unique identifier and token for controlling the call. - /// - [Input( - DisplayName = "Call Control ID", - Description = "Unique identifier and token for controlling the call.", - Category = "Advanced" - )] - public Input CallControlId { get; set; } = default!; - - /// - /// When 'dual', final audio file will be stereo recorded with the first leg on channel A, and the rest on channel B. - /// - [Input( - Description = "When 'dual', final audio file will be stereo recorded with the first leg on channel A, and the rest on channel B.", - UIHint = InputUIHints.DropDown, - Options = new[] { "single", "dual" }, - DefaultValue = "single" - )] - public Input Channels { get; set; } = new("single"); - - /// - /// The audio file format used when storing the call recording. Can be either 'mp3' or 'wav'. - /// - [Input( - Description = "The audio file format used when storing the call recording. Can be either 'mp3' or 'wav'.", - UIHint = InputUIHints.DropDown, - Options = new[] { "wav", "mp3" }, - DefaultValue = "wav" - )] - public Input Format { get; set; } = new("wav"); - - /// - /// If enabled, a beep sound will be played at the start of a recording. - /// - [Input(Description = "If enabled, a beep sound will be played at the start of a recording.")] - public Input? PlayBeep { get; set; } - - /// - protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) - { - var request = new StartRecordingRequest( - Channels.GetOrDefault(context) ?? "single", - Format.GetOrDefault(context) ?? "wav", - PlayBeep.GetOrDefault(context), - ClientState: context.CreateCorrelatingClientState() - ); - - var callControlId = CallControlId.Get(context); - var telnyxClient = context.GetRequiredService(); - - try - { - await telnyxClient.Calls.StartRecordingAsync(callControlId, request, context.CancellationToken); - - context.CreateBookmark(new WebhookEventStimulus(WebhookEventTypes.CallRecordingSaved, callControlId), ResumeAsync, false); - } - catch (ApiException e) - { - if (!await e.CallIsNoLongerActiveAsync()) throw; - await HandleDisconnectedAsync(context); - } - } - - /// - /// Called when the recording was saved. - /// - protected abstract ValueTask HandleCallRecordingSavedAsync(ActivityExecutionContext context); - - - /// - /// Called when the call was no longer active. - /// - protected abstract ValueTask HandleDisconnectedAsync(ActivityExecutionContext context); - - private async ValueTask ResumeAsync(ActivityExecutionContext context) - { - var payload = context.GetWorkflowInput(); - context.Set(Result, payload); - await HandleCallRecordingSavedAsync(context); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/StopAudioPlayback.cs b/src/modules/Elsa.Telnyx/Activities/StopAudioPlayback.cs deleted file mode 100644 index 20f22af45..000000000 --- a/src/modules/Elsa.Telnyx/Activities/StopAudioPlayback.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Workflows; -using Elsa.Workflows.Attributes; - -namespace Elsa.Telnyx.Activities; - -/// -public class StopAudioPlayback : StopAudioPlaybackBase -{ - /// - public StopAudioPlayback([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - /// The to execute when the call was no longer active. - /// - [Port] public IActivity? Disconnected { get; set; } - - /// - protected override async ValueTask HandleDoneAsync(ActivityExecutionContext context) => await context.CompleteActivityAsync(); - - /// - protected override async ValueTask HandleDisconnectedAsync(ActivityExecutionContext context) => await context.ScheduleActivityAsync(Disconnected, OnCompletedAsync); - - private async ValueTask OnCompletedAsync(ActivityCompletedContext context) => await context.TargetContext.CompleteActivityAsync(); -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/StopAudioPlaybackBase.cs b/src/modules/Elsa.Telnyx/Activities/StopAudioPlaybackBase.cs deleted file mode 100644 index 19d35f1fa..000000000 --- a/src/modules/Elsa.Telnyx/Activities/StopAudioPlaybackBase.cs +++ /dev/null @@ -1,70 +0,0 @@ -using Elsa.Extensions; -using Elsa.Telnyx.Client.Models; -using Elsa.Telnyx.Client.Services; -using Elsa.Telnyx.Extensions; -using Elsa.Workflows; -using Elsa.Workflows.Attributes; -using Elsa.Workflows.Models; -using Refit; - -namespace Elsa.Telnyx.Activities; - -/// -/// Stop audio playback. -/// -[Activity(Constants.Namespace, Description = "Stop audio playback.", Kind = ActivityKind.Task)] -public abstract class StopAudioPlaybackBase : Activity -{ - /// - protected StopAudioPlaybackBase(string? source = default, int? line = default) : base(source, line) - { - } - - /// - /// Unique identifier and token for controlling the call. - /// - [Input( - DisplayName = "Call Control ID", - Description = "Unique identifier and token for controlling the call.", - Category = "Advanced" - )] - public Input CallControlId { get; set; } = default!; - - /// - /// Use 'current' to stop only the current audio or 'all' to stop all audios in the queue. - /// - [Input( - Description = "Use 'current' to stop only the current audio or 'all' to stop all audios in the queue.", - DefaultValue = "all", - Category = "Advanced" - )] - public Input Stop { get; set; } = new("all"); - - /// - protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) - { - var request = new StopAudioPlaybackRequest(Stop.Get(context), context.CreateCorrelatingClientState(context.Id)); - var callControlId = CallControlId.Get(context); - var telnyxClient = context.GetRequiredService(); - - try - { - await telnyxClient.Calls.StopAudioPlaybackAsync(callControlId, request, context.CancellationToken); - await HandleDoneAsync(context); - } - catch (ApiException e) - { - if (!await e.CallIsNoLongerActiveAsync()) throw; - await HandleDisconnectedAsync(context); - } - } - - /// - /// Called when audio playback is stopping. - /// - protected abstract ValueTask HandleDoneAsync(ActivityExecutionContext context); - /// - /// Called when the call was no longer active. - /// - protected abstract ValueTask HandleDisconnectedAsync(ActivityExecutionContext context); -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/StopRecording.cs b/src/modules/Elsa.Telnyx/Activities/StopRecording.cs deleted file mode 100644 index dad18ae0e..000000000 --- a/src/modules/Elsa.Telnyx/Activities/StopRecording.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Extensions; -using Elsa.Telnyx.Client.Models; -using Elsa.Telnyx.Client.Services; -using Elsa.Telnyx.Extensions; -using Elsa.Workflows; -using Elsa.Workflows.Activities.Flowchart.Attributes; -using Elsa.Workflows.Attributes; -using Elsa.Workflows.Models; -using Refit; - -namespace Elsa.Telnyx.Activities; - -/// -/// Stop recording the call. -/// -[Activity(Constants.Namespace, "Stop recording the call.", Kind = ActivityKind.Task)] -[FlowNode("Recording stopped", "Disconnected")] -public class StopRecording : Activity -{ - /// - public StopRecording([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - /// Unique identifier and token for controlling the call. - /// - [Input( - DisplayName = "Call Control ID", - Description = "Unique identifier and token for controlling the call.", - Category = "Advanced" - )] - public Input CallControlId { get; set; } = default!; - - /// - protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) - { - var request = new StopRecordingRequest(context.CreateCorrelatingClientState()); - var callControlId = CallControlId.Get(context); - var telnyxClient = context.GetRequiredService(); - - try - { - await telnyxClient.Calls.StopRecordingAsync(callControlId, request, context.CancellationToken); - await context.CompleteActivityWithOutcomesAsync("Recording stopped"); - } - catch (ApiException e) - { - if (!await e.CallIsNoLongerActiveAsync()) throw; - await context.CompleteActivityWithOutcomesAsync("Disconnected"); - } - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/TransferCall.cs b/src/modules/Elsa.Telnyx/Activities/TransferCall.cs deleted file mode 100644 index bb559baee..000000000 --- a/src/modules/Elsa.Telnyx/Activities/TransferCall.cs +++ /dev/null @@ -1,162 +0,0 @@ -using System.Runtime.CompilerServices; -using Elsa.Extensions; -using Elsa.Telnyx.Attributes; -using Elsa.Telnyx.Bookmarks; -using Elsa.Telnyx.Client.Models; -using Elsa.Telnyx.Client.Services; -using Elsa.Telnyx.Extensions; -using Elsa.Telnyx.Payloads.Abstractions; -using Elsa.Telnyx.Payloads.Call; -using Elsa.Workflows; -using Elsa.Workflows.Activities.Flowchart.Attributes; -using Elsa.Workflows.Attributes; -using Elsa.Workflows.UIHints; -using Elsa.Workflows.Models; -using Refit; - -namespace Elsa.Telnyx.Activities; - -/// -/// Transfer a call to a new destination. -/// -[Activity(Constants.Namespace, "Transfer a call to a new destination.", Kind = ActivityKind.Task)] -[FlowNode("Transferred", "Hangup", "Disconnected")] -[WebhookDriven(WebhookEventTypes.CallInitiated, WebhookEventTypes.CallAnswered, WebhookEventTypes.CallHangup)] -public class TransferCall : Activity -{ - /// - public TransferCall([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - /// Unique identifier and token for controlling the call. - /// - [Input( - Name = "Call Control ID", - Description = "Unique identifier and token for controlling the call.", - Category = "Advanced" - )] - public Input CallControlId { get; set; } = default!; - - /// - /// The DID or SIP URI to dial out and bridge to the given call. - /// - [Input(Description = "The DID or SIP URI to dial out and bridge to the given call.")] - public Input To { get; set; } = default!; - - /// - /// The 'from' number to be used as the caller id presented to the destination ('To' number). The number should be in +E164 format. This attribute will default to the 'From' number of the original call if omitted. - /// - [Input(Description = "The 'from' number to be used as the caller id presented to the destination ('To' number). The number should be in +E164 format. This attribute will default to the 'From' number of the original call if omitted.")] - public Input From { get; set; } = default!; - - /// - /// The string to be used as the caller id name (SIP From Display Name) presented to the destination ('To' number). The string should have a maximum of 128 characters, containing only letters, numbers, spaces, and -_~!.+ special characters. If omitted, the display name will be the same as the number in the 'From' field. - /// - [Input(Description = - "The string to be used as the caller id name (SIP From Display Name) presented to the destination ('To' number). The string should have a maximum of 128 characters, containing only letters, numbers, spaces, and -_~!.+ special characters. If omitted, the display name will be the same as the number in the 'From' field." - )] - public Input FromDisplayName { get; set; } = default!; - - /// - /// Enables Answering Machine Detection. - /// - [Input( - DisplayName = "Answering Machine Detection", - Description = "Enables Answering Machine Detection.", - UIHint = InputUIHints.DropDown, - Options = new[] { "disabled", "detect", "detect_beep", "detect_words", "greeting_end" } - )] - public Input AnsweringMachineDetection { get; set; } = default!; - - /// - /// Audio URL to be played back when the transfer destination answers before bridging the call. The URL can point to either a WAV or MP3 file. - /// - [Input( - DisplayName = "Audio URL", - Description = "Audio URL to be played back when the transfer destination answers before bridging the call. The URL can point to either a WAV or MP3 file." - )] - public Input AudioUrl { get; set; } = default!; - - /// - /// Sets the maximum duration of a Call Control Leg in seconds. - /// - [Input(DisplayName = "Time limit", Description = "Sets the maximum duration of a Call Control Leg in seconds.", Category = "Advanced")] - public Input TimeLimitSecs { get; set; } = default!; - - /// - /// The number of seconds that Telnyx will wait for the call to be answered by the destination to which it is being transferred. - /// - [Input( - DisplayName = "Timeout", - Description = "The number of seconds that Telnyx will wait for the call to be answered by the destination to which it is being transferred.", - Category = "Advanced" - )] - public Input TimeoutSecs { get; set; } = default!; - - /// - protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) - { - await TransferCallAsync(context); - - var initiatedBookmark = new WebhookEventStimulus(WebhookEventTypes.CallInitiated); - context.CreateBookmark(initiatedBookmark, InitiatedAsync); - } - - private ValueTask InitiatedAsync(ActivityExecutionContext context) - { - var payload = context.GetWorkflowInput(); - var callControlId = payload.CallControlId; - var answeredBookmark = new WebhookEventStimulus(WebhookEventTypes.CallAnswered, callControlId); - var hangupBookmark = new WebhookEventStimulus(WebhookEventTypes.CallHangup, callControlId); - context.CreateBookmark(answeredBookmark, AnsweredAsync, false); - context.CreateBookmark(hangupBookmark, HangupAsync, false); - return default; - } - - private async ValueTask AnsweredAsync(ActivityExecutionContext context) - { - var payload = context.GetWorkflowInput(); - Result.Set(context, payload); - await context.CompleteActivityWithOutcomesAsync("Transferred"); - } - - private async ValueTask HangupAsync(ActivityExecutionContext context) - { - var payload = context.GetWorkflowInput(); - Result.Set(context, payload); - await context.CompleteActivityWithOutcomesAsync("Hangup"); - } - - private async ValueTask TransferCallAsync(ActivityExecutionContext context) - { - var callControlId = CallControlId.Get(context); - - var request = new TransferCallRequest( - To.Get(context) ?? throw new Exception("To is required."), - From.GetOrDefault(context), - FromDisplayName.GetOrDefault(context).SanitizeCallerName(), - AudioUrl.GetOrDefault(context), - AnsweringMachineDetection.GetOrDefault(context), - default, - TimeLimitSecs.GetOrDefault(context), - TimeoutSecs.GetOrDefault(context), - ClientState: context.CreateCorrelatingClientState(context.Id), - TargetLegClientState: context.CreateCorrelatingClientState(context.Id) - ); - - - var telnyxClient = context.GetRequiredService(); - - try - { - await telnyxClient.Calls.TransferCallAsync(callControlId, request, context.CancellationToken); - } - catch (ApiException e) - { - if (!await e.CallIsNoLongerActiveAsync(context.CancellationToken)) throw; - await context.CompleteActivityWithOutcomesAsync("Disconnected"); - } - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Activities/WebhookEvent.cs b/src/modules/Elsa.Telnyx/Activities/WebhookEvent.cs deleted file mode 100644 index c99dcca2b..000000000 --- a/src/modules/Elsa.Telnyx/Activities/WebhookEvent.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.ComponentModel; -using System.Runtime.CompilerServices; -using Elsa.Extensions; -using Elsa.Telnyx.Bookmarks; -using Elsa.Telnyx.Helpers; -using Elsa.Telnyx.Models; -using Elsa.Telnyx.Payloads.Abstractions; -using Elsa.Workflows; -using Elsa.Workflows.Attributes; -using Elsa.Workflows.Memory; -using Elsa.Workflows.Models; - -namespace Elsa.Telnyx.Activities; - -/// -/// Represents a Telnyx webhook event trigger. -/// -[Activity("Telnyx", "Telnyx", "A Telnyx webhook event that executes when a webhook event is received.", Kind = ActivityKind.Trigger)] -[Browsable(false)] -public class WebhookEvent : Activity -{ - /// - public WebhookEvent([CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) : base(source, line) - { - } - - /// - public WebhookEvent(string eventType, string activityTypeName, Variable result, int version = 1, [CallerFilePath] string? source = default, [CallerLineNumber] int? line = default) - : base(activityTypeName, version, source, line) - { - EventType = eventType; - Result = new(result); - } - - /// - /// The Telnyx webhook event type to listen for. - /// - [Description("The Telnyx webhook event type to listen for")] - public string EventType { get; set; } = default!; - - /// - protected override async ValueTask ExecuteAsync(ActivityExecutionContext context) - { - if (context.IsTriggerOfWorkflow()) - await Resume(context); - else - { - var eventType = EventType; - var payload = new WebhookEventStimulus(eventType); - - context.CreateBookmark(new CreateBookmarkArgs - { - Stimulus = payload, - Callback = Resume, - BookmarkName = Type - }); - } - } - - private async ValueTask Resume(ActivityExecutionContext context) - { - var input = context.GetWorkflowInput(WebhookSerializerOptions.Create()); - context.Set(Result, input.Data.Payload); - await CompleteAsync(context); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Attributes/WebhookActivityAttribute.cs b/src/modules/Elsa.Telnyx/Attributes/WebhookActivityAttribute.cs deleted file mode 100644 index e563b26be..000000000 --- a/src/modules/Elsa.Telnyx/Attributes/WebhookActivityAttribute.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Elsa.Telnyx.Payloads.Abstractions; - -namespace Elsa.Telnyx.Attributes; - -/// -/// Contains metadata about the activity descriptor to yield from the annotated payload. -/// -[AttributeUsage(AttributeTargets.Class)] -public class WebhookActivityAttribute : WebhookAttribute -{ - /// - public WebhookActivityAttribute(string eventType, string activityType, string displayName, string description) : base(eventType) - { - ActivityType = activityType; - DisplayName = displayName; - Description = description; - } - - /// - /// The activity type name to yield for the annotated type. - /// - public string ActivityType { get; } - - /// - /// The activity display name to yield for the annotated type. - /// - public string DisplayName { get; } - - /// - /// The activity description to yield for the annotated type. - /// - public string Description { get; } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Attributes/WebhookAttribute.cs b/src/modules/Elsa.Telnyx/Attributes/WebhookAttribute.cs deleted file mode 100644 index 15dd9474d..000000000 --- a/src/modules/Elsa.Telnyx/Attributes/WebhookAttribute.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Elsa.Telnyx.Payloads.Abstractions; - -namespace Elsa.Telnyx.Attributes; - -/// -/// Used to handle a Telnyx webhook event. -/// -[AttributeUsage(AttributeTargets.Class)] -public class WebhookAttribute : Attribute -{ - /// - public WebhookAttribute(string eventType) - { - EventType = eventType; - } - - /// - /// The Telnyx event to match to th annotated type. - /// - public string EventType { get; } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Attributes/WebhookDrivenAttribute.cs b/src/modules/Elsa.Telnyx/Attributes/WebhookDrivenAttribute.cs deleted file mode 100644 index 84931f816..000000000 --- a/src/modules/Elsa.Telnyx/Attributes/WebhookDrivenAttribute.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Elsa.Telnyx.Attributes; - -/// -/// Contains metadata about the activity descriptor to yield from the annotated payload. -/// -[AttributeUsage(AttributeTargets.Class)] -public class WebhookDrivenAttribute : Attribute -{ - /// - public WebhookDrivenAttribute(params string[] eventTypes) - { - EventTypes = new HashSet(eventTypes); - } - - /// - /// The Telnyx event to match. - /// - public ISet EventTypes { get; } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Bookmarks/AnswerCallStimulus.cs b/src/modules/Elsa.Telnyx/Bookmarks/AnswerCallStimulus.cs deleted file mode 100644 index 41bb81f61..000000000 --- a/src/modules/Elsa.Telnyx/Bookmarks/AnswerCallStimulus.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Elsa.Telnyx.Bookmarks; - -/// -/// A bookmark payload for the call.answered Telnyx webhook event. -/// -/// The call control ID. -public record AnswerCallStimulus(string CallControlId); \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Bookmarks/CallAnsweredStimulus.cs b/src/modules/Elsa.Telnyx/Bookmarks/CallAnsweredStimulus.cs deleted file mode 100644 index f2771fdac..000000000 --- a/src/modules/Elsa.Telnyx/Bookmarks/CallAnsweredStimulus.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Elsa.Telnyx.Bookmarks; - -/// -/// A bookmark payload for the call.answered Telnyx webhook event. -/// -/// -public record CallAnsweredStimulus(string CallControlId); \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Bookmarks/CallHangupStimulus.cs b/src/modules/Elsa.Telnyx/Bookmarks/CallHangupStimulus.cs deleted file mode 100644 index e490bda44..000000000 --- a/src/modules/Elsa.Telnyx/Bookmarks/CallHangupStimulus.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Elsa.Telnyx.Bookmarks; - -/// -/// A bookmark payload for the call.hangup Telnyx webhook event. -/// -/// -public record CallHangupStimulus(string CallControlId); \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Bookmarks/IncomingCallCatchAllStimulus.cs b/src/modules/Elsa.Telnyx/Bookmarks/IncomingCallCatchAllStimulus.cs deleted file mode 100644 index 809174800..000000000 --- a/src/modules/Elsa.Telnyx/Bookmarks/IncomingCallCatchAllStimulus.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Elsa.Telnyx.Bookmarks; - -public record IncomingCallCatchAllStimulus; \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Bookmarks/IncomingCallFromStimulus.cs b/src/modules/Elsa.Telnyx/Bookmarks/IncomingCallFromStimulus.cs deleted file mode 100644 index ca0fba4e0..000000000 --- a/src/modules/Elsa.Telnyx/Bookmarks/IncomingCallFromStimulus.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Elsa.Telnyx.Bookmarks; - -public record IncomingCallFromStimulus(string PhoneNumber); \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Bookmarks/IncomingCallToStimulus.cs b/src/modules/Elsa.Telnyx/Bookmarks/IncomingCallToStimulus.cs deleted file mode 100644 index fbc50782f..000000000 --- a/src/modules/Elsa.Telnyx/Bookmarks/IncomingCallToStimulus.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Elsa.Telnyx.Bookmarks; - -public record IncomingCallToStimulus(string PhoneNumber); \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Bookmarks/WebhookEventStimulus.cs b/src/modules/Elsa.Telnyx/Bookmarks/WebhookEventStimulus.cs deleted file mode 100644 index c2156433b..000000000 --- a/src/modules/Elsa.Telnyx/Bookmarks/WebhookEventStimulus.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Elsa.Telnyx.Bookmarks; - -/// -/// A bookmark payload for Telnyx webhook events. -/// -/// The event type. -/// An optional call control ID. -public record WebhookEventStimulus(string EventType, string? CallControlId = default); \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Client/Implementations/TelnyxClient.cs b/src/modules/Elsa.Telnyx/Client/Implementations/TelnyxClient.cs deleted file mode 100644 index 83a13e2fd..000000000 --- a/src/modules/Elsa.Telnyx/Client/Implementations/TelnyxClient.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Elsa.Telnyx.Client.Services; - -namespace Elsa.Telnyx.Client.Implementations; - -/// -/// Represents a Telnyx API client. -/// -public class TelnyxClient : ITelnyxClient -{ - /// - /// Constructor. - /// - public TelnyxClient(ICallsApi calls, INumberLookupApi numberLookup) - { - Calls = calls; - NumberLookup = numberLookup; - } - - /// - /// Provides access to the Calls API. - /// - public ICallsApi Calls { get; } - - /// - /// Provides access to the Number Lookup API. - /// - public INumberLookupApi NumberLookup { get; } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Client/Models/AnsweringMachineConfig.cs b/src/modules/Elsa.Telnyx/Client/Models/AnsweringMachineConfig.cs deleted file mode 100644 index 89239334d..000000000 --- a/src/modules/Elsa.Telnyx/Client/Models/AnsweringMachineConfig.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Elsa.Telnyx.Client.Models; - -public record AnsweringMachineConfig( - int AfterGreetingSilenceMillis = 800, - int BetweenWordsSilenceMillis = 50, - int GreetingDurationMillis = 3500, - int GreetingTotalAnalysisTimeMillis = 5000, - int InitialSilenceMillis = 3500, - int MaximumNumberOfWords = 5, - int MaximumWordLengthMillis = 3500, - int SilenceThreshold = 256, - int TotalAnalysisTimeMillis = 3500 -); \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Client/Models/Errors.cs b/src/modules/Elsa.Telnyx/Client/Models/Errors.cs deleted file mode 100644 index 0638af011..000000000 --- a/src/modules/Elsa.Telnyx/Client/Models/Errors.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Elsa.Telnyx.Client.Models; - -public record ErrorResponse(IList Errors); -public record Error(string Code, string Title, string Detail); - -public static class ErrorCodes -{ - public const string CallHasAlreadyEnded = "90018"; -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Client/Models/Header.cs b/src/modules/Elsa.Telnyx/Client/Models/Header.cs deleted file mode 100644 index d3669d0af..000000000 --- a/src/modules/Elsa.Telnyx/Client/Models/Header.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Elsa.Telnyx.Client.Models; - -public record Header(string Name, string Value); \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Client/Models/PlayAudioResponse.cs b/src/modules/Elsa.Telnyx/Client/Models/PlayAudioResponse.cs deleted file mode 100644 index e0d6fe24c..000000000 --- a/src/modules/Elsa.Telnyx/Client/Models/PlayAudioResponse.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Elsa.Telnyx.Client.Models; - -public record PlayAudioResponse(string Result); \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Client/Models/Requests.cs b/src/modules/Elsa.Telnyx/Client/Models/Requests.cs deleted file mode 100644 index 80e376b40..000000000 --- a/src/modules/Elsa.Telnyx/Client/Models/Requests.cs +++ /dev/null @@ -1,126 +0,0 @@ -namespace Elsa.Telnyx.Client.Models; - -public record AnswerCallRequest( - string? BillingGroupId = default, - string? ClientState = default, - string? CommandId = default, - string? WebhookUrl = default, - string? WebhookUrlMethod = default); - -public record HangupCallRequest(string? ClientState = default, string? CommandId = default); - -public record GatherUsingAudioRequest( - Uri AudioUrl, - string? ClientState = default, - string? CommandId = default, - int? InterDigitTimeoutMillis = default, - Uri? InvalidAudioUrl = default, - int? MaximumDigits = default, - int? MaximumTries = default, - int? MinimumDigits = default, - string? TerminatingDigit = default, - int? TimeoutMillis = default, - string? ValidDigits = default -); - -public record GatherUsingSpeakRequest( - string Language, - string Voice, - string Payload, - string? PayloadType = default, - string? ServiceLevel = default, - int? InterDigitTimeoutMillis = default, - int? MaximumDigits = default, - int? MaximumTries = default, - int? MinimumDigits = default, - string? TerminatingDigit = default, - int? TimeoutMillis = default, - string? ValidDigits = default, - string? ClientState = default, - string? CommandId = default -); - -public record TransferCallRequest( - string To, - string? From = default, - string? FromDisplayName = default, - Uri? AudioUrl = default, - string? AnsweringMachineDetection = default, - AnsweringMachineConfig? AnsweringMachineConfig = default, - int? TimeLimitSecs = default, - int? TimeoutSecs = default, - string? TargetLegClientState = default, - IList
? CustomHeaders = default, - string? SipAuthUsername = default, - string? SipAuthPassword = default, - string? ClientState = default, - string? CommandId = default, - string? WebhookUrl = default, - string? WebhookUrlMethod = default -); - -public record DialRequest( - string ConnectionId, - string To, - string? From = default, - string? FromDisplayName = default, - string? AnsweringMachineDetection = default, - AnsweringMachineConfig? AnsweringMachineConfig = default, - string? Record = default, - string? RecordFormat = default, - string? ClientState = default, - string? CommandId = default, - IList
? CustomHeaders = default, - string? SipAuthUsername = default, - string? SipAuthPassword = default, - int? TimeLimitSecs = default, - int? TimeoutSecs = default, - string? WebhookUrl = default, - string? WebhookUrlMethod = default -); - -public record BridgeCallsRequest( - string CallControlId, - string? ClientState = default, - string? CommandId = default, - string? ParkAfterUnbridge = default -); - -public record PlayAudioRequest( - Uri AudioUrl, - bool Overlay, - object? Loop = default, - string? TargetLegs = default, - string? ClientState = default, - string? CommandId = default -); - -public record StopAudioPlaybackRequest( - string? Stop = default, - string? ClientState = default, - string? CommandId = default -); - -public record StartRecordingRequest( - string Channels, - string Format, - bool? PlayBeep = default, - string? ClientState = default, - string? CommandId = default -); - -public record StopRecordingRequest( - string? ClientState = default, - string? CommandId = default -); - -public record SpeakTextRequest( - string Language, - string Voice, - string Payload, - string? PayloadType = default, - string? ServiceLevel = default, - string? Stop = default, - string? ClientState = default, - string? CommandId = default -); \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Client/Models/Responses.cs b/src/modules/Elsa.Telnyx/Client/Models/Responses.cs deleted file mode 100644 index 84220328a..000000000 --- a/src/modules/Elsa.Telnyx/Client/Models/Responses.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Elsa.Telnyx.Client.Models; - -public record TelnyxResponse(T Data); - -public record CallStatusResponse( - string CallControlId, - string CallLegId, - string CallSessionId, - string ClientState, - bool IsAlive, - string RecordType -); - -public record DialResponse( - string CallControlId, - string CallLegId, - string CallSessionId, - bool IsAlive, - string RecordType -) -{ - [JsonConstructor] - public DialResponse() : this(default!, default!, default!, default, default!) - { - } -} - -public record NumberLookupResponse( - CallerName CallerName, - Carrier Carrier, - string CountryCode, - string Fraud, - string NationalFormat, - string PhoneNumber, - Portability Portability, - string RecordType -) -{ - [JsonConstructor] - public NumberLookupResponse() : this(default!, default!, default!, default!, default!, default!, default!, default!) - { - } -} - -public record Portability( - string Altspid, - string AltspidCarrierName, - string AltspidCarrierType, - string City, - string LineType, - string Lrn, - string Ocn, - string? PortedDate, - string PortedStatus, - string Spid, - string SpidCarrierName, - string SpidCarrierType, - string State -) -{ - [JsonConstructor] - public Portability() : this(default!, default!, default!, default!, default!, default!, default!, default, default!, default!, default!, default!, default!) - { - } -} - -public record Carrier( - string ErrorCode, - string MobileCountryCode, - string MobileNetworkCode, - string Name, - string Type -) -{ - [JsonConstructor] - public Carrier() : this(default!, default!, default, default!, default!) - { - } -} - -public record CallerName -{ - [JsonPropertyName("caller_name")] public string Name { get; set; } = default!; - public string ErrorCode { get; set; } = default!; -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Client/Services/ICallsApi.cs b/src/modules/Elsa.Telnyx/Client/Services/ICallsApi.cs deleted file mode 100644 index c5aff0d7b..000000000 --- a/src/modules/Elsa.Telnyx/Client/Services/ICallsApi.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Elsa.Telnyx.Client.Models; -using Refit; - -namespace Elsa.Telnyx.Client.Services; - -public interface ICallsApi -{ - [Get("/v2/calls/{callControlId}")] - Task> GetStatusAsync(string callControlId, CancellationToken cancellationToken = default); - - [Post("/v2/calls")] - Task> DialAsync([Body] DialRequest request, CancellationToken cancellationToken = default); - - [Post("/v2/calls/{callControlId}/actions/answer")] - Task AnswerCallAsync(string callControlId, [Body] AnswerCallRequest request, CancellationToken cancellationToken = default); - - [Post("/v2/calls/{callControlId}/actions/transfer")] - Task TransferCallAsync(string callControlId, [Body] TransferCallRequest request, CancellationToken cancellationToken = default); - - [Post("/v2/calls/{callControlId}/actions/hangup")] - Task HangupCallAsync(string callControlId, [Body] HangupCallRequest request, CancellationToken cancellationToken = default); - - [Post("/v2/calls/{callControlId}/actions/gather_using_audio")] - Task GatherUsingAudioAsync(string callControlId, [Body] GatherUsingAudioRequest request, CancellationToken cancellationToken = default); - - [Post("/v2/calls/{callControlId}/actions/gather_using_speak")] - Task GatherUsingSpeakAsync(string callControlId, [Body] GatherUsingSpeakRequest request, CancellationToken cancellationToken = default); - - [Post("/v2/calls/{callControlId}/actions/bridge")] - Task BridgeCallsAsync(string callControlId, [Body] BridgeCallsRequest request, CancellationToken cancellationToken = default); - - [Post("/v2/calls/{callControlId}/actions/playback_start")] - Task> PlayAudioAsync(string callControlId, [Body] PlayAudioRequest request, CancellationToken cancellationToken = default); - - [Post("/v2/calls/{callControlId}/actions/playback_stop")] - Task StopAudioPlaybackAsync(string callControlId, [Body] StopAudioPlaybackRequest 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/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); -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Client/Services/INumberLookupApi.cs b/src/modules/Elsa.Telnyx/Client/Services/INumberLookupApi.cs deleted file mode 100644 index 7fab11837..000000000 --- a/src/modules/Elsa.Telnyx/Client/Services/INumberLookupApi.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Elsa.Telnyx.Client.Models; -using Refit; - -namespace Elsa.Telnyx.Client.Services; - -public interface INumberLookupApi -{ - [Get("/v2/number_lookup/{phoneNumber}")] - Task> NumberLookupAsync( - string phoneNumber, - [Query(CollectionFormat.Multi)] [AliasAs("type")] - IEnumerable? types = default, - CancellationToken cancellationToken = default); -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Client/Services/ITelnyxClient.cs b/src/modules/Elsa.Telnyx/Client/Services/ITelnyxClient.cs deleted file mode 100644 index eca11cf5d..000000000 --- a/src/modules/Elsa.Telnyx/Client/Services/ITelnyxClient.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Elsa.Telnyx.Client.Services; - -public interface ITelnyxClient -{ - ICallsApi Calls { get; } - INumberLookupApi NumberLookup { get; } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Constants.cs b/src/modules/Elsa.Telnyx/Constants.cs deleted file mode 100644 index c56c8378a..000000000 --- a/src/modules/Elsa.Telnyx/Constants.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Elsa.Telnyx; - -/// -/// Contains constants used by the Telnyx activities. -/// -public static class Constants -{ - /// - /// The namespace used by the Telnyx activities. - /// - public const string Namespace = "Telnyx"; - - /// - /// The category used by the Telnyx activities. - /// - public const string Category = "Telnyx"; -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Contracts/IWebhookHandler.cs b/src/modules/Elsa.Telnyx/Contracts/IWebhookHandler.cs deleted file mode 100644 index df90d051b..000000000 --- a/src/modules/Elsa.Telnyx/Contracts/IWebhookHandler.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.AspNetCore.Http; - -namespace Elsa.Telnyx.Contracts; - -internal interface IWebhookHandler -{ - Task HandleAsync(HttpContext httpContext); -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Elsa.Telnyx.csproj b/src/modules/Elsa.Telnyx/Elsa.Telnyx.csproj deleted file mode 100644 index 9633deb6c..000000000 --- a/src/modules/Elsa.Telnyx/Elsa.Telnyx.csproj +++ /dev/null @@ -1,25 +0,0 @@ - - - - - Provides integration with Telnyx. - - elsa module telephony telnyx - - - - - - - - - - - - - - - - - - diff --git a/src/modules/Elsa.Telnyx/Events/TelnyxWebhookReceived.cs b/src/modules/Elsa.Telnyx/Events/TelnyxWebhookReceived.cs deleted file mode 100644 index 7bc09274a..000000000 --- a/src/modules/Elsa.Telnyx/Events/TelnyxWebhookReceived.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Elsa.Mediator.Contracts; -using Elsa.Telnyx.Models; - -namespace Elsa.Telnyx.Events; - -/// -/// Triggered when a Telnyx webhook is received. -/// -public class TelnyxWebhookReceived : INotification -{ - /// - /// Initializes a new instance of the class. - /// - public TelnyxWebhookReceived(TelnyxWebhook webhook) - { - Webhook = webhook; - } - - /// - /// Gets the webhook. - /// - public TelnyxWebhook Webhook { get; } - -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Exceptions/MissingCallControlAppIdException.cs b/src/modules/Elsa.Telnyx/Exceptions/MissingCallControlAppIdException.cs deleted file mode 100644 index e380864fb..000000000 --- a/src/modules/Elsa.Telnyx/Exceptions/MissingCallControlAppIdException.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Elsa.Telnyx.Exceptions; - -public class MissingCallControlAppIdException : TelnyxException -{ - public MissingCallControlAppIdException(string message, Exception? innerException = default) : base(message, innerException) - { - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Exceptions/MissingCallControlIdException.cs b/src/modules/Elsa.Telnyx/Exceptions/MissingCallControlIdException.cs deleted file mode 100644 index af4f65b59..000000000 --- a/src/modules/Elsa.Telnyx/Exceptions/MissingCallControlIdException.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Elsa.Telnyx.Exceptions; - -public class MissingCallControlIdException : TelnyxException -{ - public MissingCallControlIdException(string message, Exception? innerException = default) : base(message, innerException) - { - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Exceptions/MissingFromNumberException.cs b/src/modules/Elsa.Telnyx/Exceptions/MissingFromNumberException.cs deleted file mode 100644 index 30bdb9c3a..000000000 --- a/src/modules/Elsa.Telnyx/Exceptions/MissingFromNumberException.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Elsa.Telnyx.Exceptions; - -public class MissingFromNumberException : TelnyxException -{ - public MissingFromNumberException(string message, Exception? innerException = default) : base(message, innerException) - { - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Exceptions/TelnyxException.cs b/src/modules/Elsa.Telnyx/Exceptions/TelnyxException.cs deleted file mode 100644 index 41f7aaf42..000000000 --- a/src/modules/Elsa.Telnyx/Exceptions/TelnyxException.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Elsa.Telnyx.Exceptions; - -public class TelnyxException : Exception -{ - public TelnyxException(string message, Exception? innerException = default) : base(message, innerException) - { - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Extensions/ActivityExecutionExtensions.cs b/src/modules/Elsa.Telnyx/Extensions/ActivityExecutionExtensions.cs deleted file mode 100644 index a85dcd6b6..000000000 --- a/src/modules/Elsa.Telnyx/Extensions/ActivityExecutionExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Elsa.Telnyx.Models; -using Elsa.Workflows; - -namespace Elsa.Telnyx.Extensions; - -/// -/// Provides extensions on . -/// -public static class ActivityExecutionExtensions -{ - /// - /// Creates a correlating client state. - /// - public static string CreateCorrelatingClientState(this ActivityExecutionContext context, string? activityInstanceId = default) - { - return new ClientStatePayload(context.WorkflowExecutionContext.Id, activityInstanceId).ToBase64(); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Extensions/ApiExceptionExtensions.cs b/src/modules/Elsa.Telnyx/Extensions/ApiExceptionExtensions.cs deleted file mode 100644 index c94e97482..000000000 --- a/src/modules/Elsa.Telnyx/Extensions/ApiExceptionExtensions.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Elsa.Telnyx.Client.Models; -using Refit; - -namespace Elsa.Telnyx.Extensions; - -/// -/// Provides extensions for . -/// -public static class ApiExceptionExtensions -{ - /// - /// Reads a from the specified . - /// - public static async Task GetErrorResponseAsync(this ApiException e, CancellationToken cancellationToken = default) - { - var httpContent = new StringContent(e.Content!); - return (await e.RefitSettings.ContentSerializer.FromHttpContentAsync(httpContent, cancellationToken))!; - } - - /// - /// Returns true if the specified exception represents a failure due to the call no longer being active. - /// - public static async Task CallIsNoLongerActiveAsync(this ApiException e, CancellationToken cancellationToken = default) - { - var errorResponse = await e.GetErrorResponseAsync(cancellationToken); - var errors = errorResponse.Errors; - return errors.Any(x => x.Code == ErrorCodes.CallHasAlreadyEnded); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Extensions/DependencyInjectionExtensions.cs b/src/modules/Elsa.Telnyx/Extensions/DependencyInjectionExtensions.cs deleted file mode 100644 index fe99b9241..000000000 --- a/src/modules/Elsa.Telnyx/Extensions/DependencyInjectionExtensions.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System.Net.Http.Headers; -using System.Text.Json; -using System.Text.Json.Serialization; -using Elsa.Telnyx.Client.Implementations; -using Elsa.Telnyx.Client.Services; -using Elsa.Telnyx.Contracts; -using Elsa.Telnyx.Handlers; -using Elsa.Telnyx.Options; -using Elsa.Telnyx.Serialization; -using Elsa.Telnyx.Services; -using Microsoft.Extensions.Options; -using Refit; - -// ReSharper disable once CheckNamespace -namespace Microsoft.Extensions.DependencyInjection; - -/// -/// Provides service dependency extensions that register required services for Telnyx integration. -/// -public static class DependencyInjectionExtensions -{ - /// - /// Adds Telnyx services to the service container. - /// - /// - public static IServiceCollection AddTelnyx( - this IServiceCollection services, - Action? configure = default, - Func? httpClientFactory = default, - Action? configureHttpClientBuilder = default) - { - // Telnyx options. - configure ??= options => options.ApiUrl = new Uri("https://api.telnyx.com"); - services.Configure(configure); - - // Services. - services - .AddNotificationHandlersFrom() - .AddScoped(); - - // Telnyx API Client. - var refitSettings = CreateRefitSettings(); - - services - .AddApiClient(refitSettings, httpClientFactory, configureHttpClientBuilder) - .AddApiClient(refitSettings, httpClientFactory, configureHttpClientBuilder) - .AddTransient(); - - return services; - } - - /// - /// Registers the specified interface type as a Refit client. - /// - private static IServiceCollection AddApiClient( - this IServiceCollection services, - RefitSettings refitSettings, - Func? httpClientFactory, - Action? configureHttpClientBuilder) where T : class - { - if (httpClientFactory == null) - { - var httpClientBuilder = services.AddRefitClient(refitSettings).ConfigureHttpClient((sp, client) => - { - var options = sp.GetRequiredService>().Value; - client.BaseAddress = options.ApiUrl; - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", options.ApiKey); - }); - - configureHttpClientBuilder?.Invoke(httpClientBuilder); - } - else - { - services.AddScoped(sp => - { - var httpClient = httpClientFactory(sp); - var options = sp.GetRequiredService>().Value; - httpClient.BaseAddress ??= options.ApiUrl; - httpClient.DefaultRequestHeaders.Authorization ??= new AuthenticationHeaderValue("Bearer", options.ApiKey); - - return RestService.For(httpClient, refitSettings); - }); - } - - return services; - } - - private static RefitSettings CreateRefitSettings() - { - var serializerSettings = new JsonSerializerOptions() - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - PropertyNamingPolicy = new SnakeCaseNamingPolicy(), - }; - - serializerSettings.Converters.Add(new WebhookDataJsonConverter()); - - return new RefitSettings - { - ContentSerializer = new SystemTextJsonContentSerializer(serializerSettings) - }; - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Extensions/EndpointsExtensions.cs b/src/modules/Elsa.Telnyx/Extensions/EndpointsExtensions.cs deleted file mode 100644 index 2d50ea05d..000000000 --- a/src/modules/Elsa.Telnyx/Extensions/EndpointsExtensions.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Elsa.Telnyx.Contracts; -using JetBrains.Annotations; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.DependencyInjection; - -// ReSharper disable once CheckNamespace -namespace Elsa.Extensions; - -/// -/// Provides extensions on -/// -[PublicAPI] -public static class EndpointsExtensions -{ - /// - /// Maps the specified route to the Telnyx webhook handler. - /// - public static IEndpointConventionBuilder UseTelnyxWebhooks(this IEndpointRouteBuilder endpoints, string routePattern = "telnyx-hook") - { - return endpoints.MapPost(routePattern, HandleTelnyxRequest); - } - - private static async Task HandleTelnyxRequest(HttpContext context) - { - var services = context.RequestServices; - var webhookHandler = services.GetRequiredService(); - await webhookHandler.HandleAsync(context); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Extensions/ModuleExtensions.cs b/src/modules/Elsa.Telnyx/Extensions/ModuleExtensions.cs deleted file mode 100644 index 2b63cb95d..000000000 --- a/src/modules/Elsa.Telnyx/Extensions/ModuleExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Elsa.Features.Services; -using Elsa.Telnyx.Features; - -// ReSharper disable once CheckNamespace -namespace Elsa.Extensions; - -public static class ModuleExtensions -{ - public static IModule UseTelnyx(this IModule module, Action? configure = default) - { - module.Configure(configure); - return module; - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Extensions/PayloadExtensions.cs b/src/modules/Elsa.Telnyx/Extensions/PayloadExtensions.cs deleted file mode 100644 index 4c7e547b2..000000000 --- a/src/modules/Elsa.Telnyx/Extensions/PayloadExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Elsa.Telnyx.Models; -using Elsa.Telnyx.Payloads.Abstractions; - -namespace Elsa.Telnyx.Extensions; - -/// -/// Provides extensions on . -/// -public static class PayloadExtensions -{ - /// - /// Extracts a from the specified . - /// - public static ClientStatePayload? GetClientStatePayload(this Payload payload) - { - return !string.IsNullOrWhiteSpace(payload.ClientState) - ? ClientStatePayload.FromBase64(payload.ClientState) - : default; - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Extensions/StringExtensions.cs b/src/modules/Elsa.Telnyx/Extensions/StringExtensions.cs deleted file mode 100644 index 6fd2f492a..000000000 --- a/src/modules/Elsa.Telnyx/Extensions/StringExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Text.RegularExpressions; -using Humanizer; - -namespace Elsa.Telnyx.Extensions; - -public static class StringExtensions -{ - private static readonly Regex WhiteList = new(@"[^a-zA-Z0-9-_.!~+ ]"); - - /// - /// Sanitizes the caller name. - /// - public static string? SanitizeCallerName(this string? value) => value == null ? null : WhiteList.Replace(value, "").Truncate(128); - - /// - /// Returns null if the specifies string is empty. - /// - public static string? EmptyToNull(this string? value) => value is "" ? null : value; -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Features/TelnyxFeature.cs b/src/modules/Elsa.Telnyx/Features/TelnyxFeature.cs deleted file mode 100644 index 3b49d8358..000000000 --- a/src/modules/Elsa.Telnyx/Features/TelnyxFeature.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.ComponentModel; -using System.Reflection; -using Elsa.Extensions; -using Elsa.Features.Abstractions; -using Elsa.Features.Services; -using Elsa.Telnyx.Activities; -using Elsa.Telnyx.Client.Models; -using Elsa.Telnyx.Options; -using Elsa.Telnyx.Payloads.Abstractions; -using Elsa.Telnyx.Providers; -using Microsoft.Extensions.DependencyInjection; - -namespace Elsa.Telnyx.Features; - -/// -/// Enables Telnyx integration. -/// -public class TelnyxFeature : FeatureBase -{ - private const string TelnyxCategoryName = "Telnyx"; - - /// - public TelnyxFeature(IModule module) : base(module) - { - } - - /// - /// Configures Telnyx options. - /// - public Action ConfigureTelnyxOptions { get; set; } = _ => { }; - - /// - /// Gets or sets a factory that creates an used to communicate with the Telnyx API. - /// - public Func? HttpClientFactory { get; set; } - - /// - /// Configures the used to communicate with the Telnyx API. - /// - public Action? ConfigureHttpClientBuilder { get; set; } - - /// - public override void Configure() - { - Module.UseWorkflowManagement(management => - { - management.AddActivitiesFrom(); - - management.AddVariableTypes(typeof(TelnyxFeature).Assembly.ExportedTypes.Where(x => - { - var browsableAttr = x.GetCustomAttribute(); - return typeof(Payload).IsAssignableFrom(x) && browsableAttr == null || browsableAttr?.Browsable == true; - }), TelnyxCategoryName); - - management.AddVariableType(TelnyxCategoryName); - management.AddVariableType(TelnyxCategoryName); - management.AddVariableType(TelnyxCategoryName); - management.AddVariableType(TelnyxCategoryName); - }); - } - - /// - public override void Apply() - { - Services - .AddTelnyx(ConfigureTelnyxOptions, HttpClientFactory, ConfigureHttpClientBuilder) - .AddActivityProvider(); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/FodyWeavers.xml b/src/modules/Elsa.Telnyx/FodyWeavers.xml deleted file mode 100644 index 00e1d9a1c..000000000 --- a/src/modules/Elsa.Telnyx/FodyWeavers.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Handlers/TriggerAnswerCallActivities.cs b/src/modules/Elsa.Telnyx/Handlers/TriggerAnswerCallActivities.cs deleted file mode 100644 index e9fb6d731..000000000 --- a/src/modules/Elsa.Telnyx/Handlers/TriggerAnswerCallActivities.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Elsa.Extensions; -using Elsa.Mediator.Contracts; -using Elsa.Telnyx.Activities; -using Elsa.Telnyx.Bookmarks; -using Elsa.Telnyx.Events; -using Elsa.Telnyx.Extensions; -using Elsa.Telnyx.Payloads.Call; -using Elsa.Workflows.Helpers; -using Elsa.Workflows.Runtime; -using JetBrains.Annotations; -using Microsoft.Extensions.Logging; - -namespace Elsa.Telnyx.Handlers; - -/// -/// Triggers all workflows blocked on a or activity. -/// -[PublicAPI] -internal class TriggerAnswerCallActivities(IStimulusSender stimulusSender, ILogger logger) - : INotificationHandler -{ - private readonly ILogger _logger = logger; - - public async Task HandleAsync(TelnyxWebhookReceived notification, CancellationToken cancellationToken) - { - var webhook = notification.Webhook; - var payload = webhook.Data.Payload; - - if (payload is not CallAnsweredPayload callAnsweredPayload) - return; - - var clientStatePayload = callAnsweredPayload.GetClientStatePayload(); - var workflowInstanceId = clientStatePayload?.WorkflowInstanceId; - var activityInstanceId = clientStatePayload?.ActivityInstanceId!; - var input = new Dictionary().AddInput(callAnsweredPayload); - var callControlId = callAnsweredPayload.CallControlId; - - var activityTypeNames = new[] - { - ActivityTypeNameHelper.GenerateTypeName(), - ActivityTypeNameHelper.GenerateTypeName(), - }; - - foreach (var activityTypeName in activityTypeNames) - { - var stimulus = new AnswerCallStimulus(callControlId); - var metadata = new StimulusMetadata - { - WorkflowInstanceId = workflowInstanceId, - ActivityInstanceId = activityInstanceId, - Input = input - }; - await stimulusSender.SendAsync(activityTypeName, stimulus, metadata, cancellationToken); - } - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Handlers/TriggerCallAnsweredActivities.cs b/src/modules/Elsa.Telnyx/Handlers/TriggerCallAnsweredActivities.cs deleted file mode 100644 index 318832ce4..000000000 --- a/src/modules/Elsa.Telnyx/Handlers/TriggerCallAnsweredActivities.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Elsa.Extensions; -using Elsa.Mediator.Contracts; -using Elsa.Telnyx.Activities; -using Elsa.Telnyx.Bookmarks; -using Elsa.Telnyx.Events; -using Elsa.Telnyx.Extensions; -using Elsa.Telnyx.Payloads.Call; -using Elsa.Workflows.Runtime; -using JetBrains.Annotations; -using Microsoft.Extensions.Logging; - -namespace Elsa.Telnyx.Handlers; - -/// -/// Triggers all workflows starting with or blocked on a activity. -/// -[PublicAPI] -internal class TriggerCallAnsweredActivities(IStimulusSender stimulusSender, ILogger logger) : INotificationHandler -{ - private readonly ILogger _logger = logger; - - public async Task HandleAsync(TelnyxWebhookReceived notification, CancellationToken cancellationToken) - { - var webhook = notification.Webhook; - var payload = webhook.Data.Payload; - - if (payload is not CallAnsweredPayload callAnsweredPayload) - return; - - var clientStatePayload = callAnsweredPayload.GetClientStatePayload(); - var workflowInstanceId = clientStatePayload?.WorkflowInstanceId; - var activityInstanceId = clientStatePayload?.ActivityInstanceId!; - var input = new Dictionary().AddInput(callAnsweredPayload); - var callControlId = callAnsweredPayload.CallControlId; - - var stimulus = new CallAnsweredStimulus(callControlId); - var metadata = new StimulusMetadata - { - WorkflowInstanceId = workflowInstanceId, - ActivityInstanceId = activityInstanceId, - Input = input - }; - await stimulusSender.SendAsync(stimulus, metadata, cancellationToken); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Handlers/TriggerCallBridgedActivities.cs b/src/modules/Elsa.Telnyx/Handlers/TriggerCallBridgedActivities.cs deleted file mode 100644 index b209d188f..000000000 --- a/src/modules/Elsa.Telnyx/Handlers/TriggerCallBridgedActivities.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Elsa.Extensions; -using Elsa.Mediator.Contracts; -using Elsa.Telnyx.Activities; -using Elsa.Telnyx.Bookmarks; -using Elsa.Telnyx.Events; -using Elsa.Telnyx.Extensions; -using Elsa.Telnyx.Payloads.Call; -using Elsa.Workflows.Helpers; -using Elsa.Workflows.Runtime; -using JetBrains.Annotations; - -namespace Elsa.Telnyx.Handlers; - -/// -/// Triggers all workflows starting with or blocked on a activity. -/// -[PublicAPI] -internal class TriggerCallBridgedActivities(IStimulusSender stimulusSender) : INotificationHandler -{ - public async Task HandleAsync(TelnyxWebhookReceived notification, CancellationToken cancellationToken) - { - var webhook = notification.Webhook; - var payload = webhook.Data.Payload; - - if (payload is not CallBridgedPayload callBridgedPayload) - return; - - var clientStatePayload = callBridgedPayload.GetClientStatePayload(); - var workflowInstanceId = clientStatePayload?.WorkflowInstanceId; - var input = new Dictionary().AddInput(callBridgedPayload); - var callControlId = callBridgedPayload.CallControlId; - var activityTypeNames = new[] - { - ActivityTypeNameHelper.GenerateTypeName(), - ActivityTypeNameHelper.GenerateTypeName(), - }; - - foreach (var activityTypeName in activityTypeNames) - { - var stimulus = new WebhookEventStimulus(WebhookEventTypes.CallBridged, callControlId); - var metadata = new StimulusMetadata - { - WorkflowInstanceId = workflowInstanceId, - Input = input - }; - await stimulusSender.SendAsync(activityTypeName, stimulus, metadata, cancellationToken); - } - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Handlers/TriggerCallHangupActivities.cs b/src/modules/Elsa.Telnyx/Handlers/TriggerCallHangupActivities.cs deleted file mode 100644 index c2eb0ebc2..000000000 --- a/src/modules/Elsa.Telnyx/Handlers/TriggerCallHangupActivities.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Elsa.Extensions; -using Elsa.Mediator.Contracts; -using Elsa.Telnyx.Activities; -using Elsa.Telnyx.Bookmarks; -using Elsa.Telnyx.Events; -using Elsa.Telnyx.Extensions; -using Elsa.Telnyx.Payloads.Call; -using Elsa.Workflows.Runtime; -using JetBrains.Annotations; - -namespace Elsa.Telnyx.Handlers; - -/// -/// Triggers all workflows starting with or blocked on a activity. -/// -[PublicAPI] -internal class TriggerCallHangupActivities(IStimulusSender stimulusSender) - : INotificationHandler -{ - public async Task HandleAsync(TelnyxWebhookReceived notification, CancellationToken cancellationToken) - { - var webhook = notification.Webhook; - var payload = webhook.Data.Payload; - - if (payload is not CallHangupPayload callHangupPayload) - return; - - var clientStatePayload = callHangupPayload.GetClientStatePayload(); - var workflowInstanceId = clientStatePayload?.WorkflowInstanceId; - var input = new Dictionary().AddInput(callHangupPayload); - var callControlId = callHangupPayload.CallControlId; - var stimulus = new CallHangupStimulus(callControlId); - var metadata = new StimulusMetadata - { - WorkflowInstanceId = workflowInstanceId, - Input = input - }; - await stimulusSender.SendAsync(stimulus, metadata, cancellationToken); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Handlers/TriggerIncomingCallActivities.cs b/src/modules/Elsa.Telnyx/Handlers/TriggerIncomingCallActivities.cs deleted file mode 100644 index 51f3f6b1a..000000000 --- a/src/modules/Elsa.Telnyx/Handlers/TriggerIncomingCallActivities.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Elsa.Extensions; -using Elsa.Mediator.Contracts; -using Elsa.Telnyx.Activities; -using Elsa.Telnyx.Bookmarks; -using Elsa.Telnyx.Events; -using Elsa.Telnyx.Payloads.Call; -using Elsa.Workflows.Runtime; -using JetBrains.Annotations; - -namespace Elsa.Telnyx.Handlers; - -/// -/// Triggers all workflows starting with or blocked on a activity. -/// -[PublicAPI] -internal class TriggerIncomingCallActivities(IStimulusSender stimulusSender) - : INotificationHandler -{ - public async Task HandleAsync(TelnyxWebhookReceived notification, CancellationToken cancellationToken) - { - var webhook = notification.Webhook; - var payload = webhook.Data.Payload; - - if (payload is not CallInitiatedPayload callInitiatedPayload) - return; - - // Only trigger workflows for incoming calls. - if (callInitiatedPayload.Direction != "incoming" || callInitiatedPayload.ClientState != null) - return; - - var input = new Dictionary().AddInput(webhook); - var stimulusMetadata = new StimulusMetadata - { - Input = input - }; - - // Trigger all workflows matching the From number. - var fromNumber = callInitiatedPayload.From; - var fromStimulus = new IncomingCallFromStimulus(fromNumber); - var fromResults = await stimulusSender.SendAsync(fromStimulus, stimulusMetadata, cancellationToken); - - // Trigger all workflows matching the To number. - var toNumber = callInitiatedPayload.To; - var toStimulus = new IncomingCallToStimulus(toNumber); - var toResults = await stimulusSender.SendAsync(toStimulus, stimulusMetadata, cancellationToken); - - // If any workflows were triggered, don't trigger the catch-all workflows. - if (fromResults.WorkflowInstanceResponses.Any() || toResults.WorkflowInstanceResponses.Any()) - return; - - // Trigger all catch-all workflows. - var catchAllStimulus = new IncomingCallCatchAllStimulus(); - await stimulusSender.SendAsync(catchAllStimulus, stimulusMetadata, cancellationToken); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Handlers/TriggerWebhookActivities.cs b/src/modules/Elsa.Telnyx/Handlers/TriggerWebhookActivities.cs deleted file mode 100644 index 9b07c185c..000000000 --- a/src/modules/Elsa.Telnyx/Handlers/TriggerWebhookActivities.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.Reflection; -using Elsa.Extensions; -using Elsa.Mediator.Contracts; -using Elsa.Telnyx.Activities; -using Elsa.Telnyx.Attributes; -using Elsa.Telnyx.Bookmarks; -using Elsa.Telnyx.Events; -using Elsa.Telnyx.Extensions; -using Elsa.Telnyx.Payloads.Abstractions; -using Elsa.Workflows.Runtime; -using JetBrains.Annotations; - -namespace Elsa.Telnyx.Handlers; - -/// -/// Triggers all workflows starting with or blocked on a activity. -/// -[PublicAPI] -internal class TriggerWebhookActivities(IStimulusSender stimulusSender) : INotificationHandler -{ - public async Task HandleAsync(TelnyxWebhookReceived notification, CancellationToken cancellationToken) - { - var webhook = notification.Webhook; - var eventType = webhook.Data.EventType; - var payload = webhook.Data.Payload; - var activityType = payload.GetType().GetCustomAttribute()?.ActivityType; - - if (activityType == null) - return; - - var workflowInstanceId = ((Payload)webhook.Data.Payload).GetClientStatePayload()?.WorkflowInstanceId; - var callControlId = (webhook.Data.Payload as CallPayload)?.CallControlId; - var stimulus = new WebhookEventStimulus(eventType, callControlId); - var input = new Dictionary().AddInput(webhook); - - var metadata = new StimulusMetadata - { - Input = input, - WorkflowInstanceId = workflowInstanceId, - - }; - await stimulusSender.SendAsync(activityType, stimulus, metadata, cancellationToken); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Handlers/TriggerWebhookDrivenActivities.cs b/src/modules/Elsa.Telnyx/Handlers/TriggerWebhookDrivenActivities.cs deleted file mode 100644 index 9fa0fdd01..000000000 --- a/src/modules/Elsa.Telnyx/Handlers/TriggerWebhookDrivenActivities.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Elsa.Extensions; -using Elsa.Mediator.Contracts; -using Elsa.Telnyx.Attributes; -using Elsa.Telnyx.Bookmarks; -using Elsa.Telnyx.Events; -using Elsa.Telnyx.Extensions; -using Elsa.Telnyx.Payloads.Abstractions; -using Elsa.Workflows; -using Elsa.Workflows.Models; -using Elsa.Workflows.Runtime; -using JetBrains.Annotations; - -namespace Elsa.Telnyx.Handlers; - -/// -/// Resumes all workflows blocked on activities that are waiting for a given webhook. -/// -[PublicAPI] -internal class TriggerWebhookDrivenActivities(IStimulusSender stimulusSender, IActivityRegistry activityRegistry) - : INotificationHandler -{ - public async Task HandleAsync(TelnyxWebhookReceived notification, CancellationToken cancellationToken) - { - var webhook = notification.Webhook; - var eventType = webhook.Data.EventType; - var eventPayload = webhook.Data.Payload; - var callPayload = eventPayload as CallPayload; - var callControlId = callPayload?.CallControlId; - var input = new Dictionary().AddInput(eventPayload.GetType().Name, eventPayload); - var activityDescriptors = FindActivityDescriptors(eventType).ToList(); - var clientStatePayload = ((Payload)webhook.Data.Payload).GetClientStatePayload(); - var activityInstanceId = clientStatePayload?.ActivityInstanceId; - var workflowInstanceId = clientStatePayload?.WorkflowInstanceId; - var bookmarkPayloadWithCallControl = new WebhookEventStimulus(eventType, callControlId); - - foreach (var activityDescriptor in activityDescriptors) - { - var metadata = new StimulusMetadata - { - WorkflowInstanceId = workflowInstanceId, - ActivityInstanceId = activityInstanceId, - Input = input - }; - - await stimulusSender.SendAsync(activityDescriptor.TypeName, bookmarkPayloadWithCallControl, metadata, cancellationToken); - } - } - - private IEnumerable FindActivityDescriptors(string eventType) => - activityRegistry.FindMany(descriptor => descriptor.GetAttribute()?.EventTypes.Contains(eventType) == true); -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Helpers/PayloadSerializer.cs b/src/modules/Elsa.Telnyx/Helpers/PayloadSerializer.cs deleted file mode 100644 index 3d03c0ebb..000000000 --- a/src/modules/Elsa.Telnyx/Helpers/PayloadSerializer.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Text.Json; -using Elsa.Telnyx.Payloads; -using Elsa.Telnyx.Payloads.Abstractions; - -namespace Elsa.Telnyx.Helpers; - -internal static class PayloadSerializer -{ - public static Payload Deserialize(string eventType, JsonElement dataModel, JsonSerializerOptions? options = null) - { - var payloadType = WebhookPayloadTypes.PayloadTypeDictionary.TryGetValue(eventType, out var value) ? value : typeof(UnsupportedPayload); - return (Payload)dataModel.Deserialize(payloadType, options)!; - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Helpers/WebhookPayloadTypes.cs b/src/modules/Elsa.Telnyx/Helpers/WebhookPayloadTypes.cs deleted file mode 100644 index bb7370139..000000000 --- a/src/modules/Elsa.Telnyx/Helpers/WebhookPayloadTypes.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Reflection; -using Elsa.Telnyx.Attributes; -using Elsa.Telnyx.Payloads.Abstractions; - -namespace Elsa.Telnyx.Helpers; - -/// -/// A helper class related to webhook event payload discovery. -/// -public static class WebhookPayloadTypes -{ - /// - /// A list of types. - /// - public static readonly ICollection PayloadTypes; - - /// - /// A dictionary of event types mapped to payload types. - /// - public static readonly IDictionary PayloadTypeDictionary; - - static WebhookPayloadTypes() - { - PayloadTypes = typeof(WebhookPayloadTypes).Assembly.GetTypes().Where(x => typeof(Payload).IsAssignableFrom(x) && x.GetCustomAttribute(true) != null).ToList(); - - var query = - from payloadType in PayloadTypes - let payloadAttribute = payloadType.GetCustomAttribute() - where payloadAttribute != null - select (payloadType, payloadAttribute); - - PayloadTypeDictionary = query.ToDictionary(x => x.payloadAttribute!.EventType, x => x.payloadType); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Helpers/WebhookSerializerOptions.cs b/src/modules/Elsa.Telnyx/Helpers/WebhookSerializerOptions.cs deleted file mode 100644 index 99a2112af..000000000 --- a/src/modules/Elsa.Telnyx/Helpers/WebhookSerializerOptions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Text.Json; -using Elsa.Telnyx.Serialization; - -namespace Elsa.Telnyx.Helpers; - -internal static class WebhookSerializerOptions -{ - public static JsonSerializerOptions Create() - { - var options = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true, - PropertyNamingPolicy = new SnakeCaseNamingPolicy() - }; - - options.Converters.Add(new WebhookDataJsonConverter()); - return options; - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Models/BridgedCallsOutput.cs b/src/modules/Elsa.Telnyx/Models/BridgedCallsOutput.cs deleted file mode 100644 index abf46912a..000000000 --- a/src/modules/Elsa.Telnyx/Models/BridgedCallsOutput.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Elsa.Telnyx.Activities; -using Elsa.Telnyx.Payloads.Call; - -namespace Elsa.Telnyx.Models; - -/// -/// Contains output of the activity. -/// -/// The payload from leg A. -/// The payload from leg B. -public record BridgedCallsOutput(CallBridgedPayload PayloadA, CallBridgedPayload PayloadB); \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Models/CallRecordingUrls.cs b/src/modules/Elsa.Telnyx/Models/CallRecordingUrls.cs deleted file mode 100644 index 97433d2b1..000000000 --- a/src/modules/Elsa.Telnyx/Models/CallRecordingUrls.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Elsa.Telnyx.Models; - -public record CallRecordingUrls(string? Wav, string? Mp3); \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Models/ClientStatePayload.cs b/src/modules/Elsa.Telnyx/Models/ClientStatePayload.cs deleted file mode 100644 index aefb6a06f..000000000 --- a/src/modules/Elsa.Telnyx/Models/ClientStatePayload.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Text; -using System.Text.Json; - -namespace Elsa.Telnyx.Models; - -/// -/// Represents the client state payload to correlate commands and events. -/// -/// The correlation ID. -/// An optional activity instance ID. -public record ClientStatePayload(string WorkflowInstanceId, string? ActivityInstanceId = default) -{ - /// - /// Deserializes a from the specified base64 string. - /// - public static ClientStatePayload FromBase64(string base64) - { - var bytes = Convert.FromBase64String(base64); - var json = Encoding.UTF8.GetString(bytes); - return JsonSerializer.Deserialize(json)!; - } - - /// - /// Serializes the to a base64 string. - /// - /// - public string ToBase64() - { - var json = JsonSerializer.Serialize(this); - var bytes = Encoding.UTF8.GetBytes(json); - return Convert.ToBase64String(bytes); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Models/TelnyxRecord.cs b/src/modules/Elsa.Telnyx/Models/TelnyxRecord.cs deleted file mode 100644 index abd97e524..000000000 --- a/src/modules/Elsa.Telnyx/Models/TelnyxRecord.cs +++ /dev/null @@ -1,3 +0,0 @@ -namespace Elsa.Telnyx.Models; - -public abstract record TelnyxRecord(string RecordType); \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Models/TelnyxWebhook.cs b/src/modules/Elsa.Telnyx/Models/TelnyxWebhook.cs deleted file mode 100644 index 645fcdba0..000000000 --- a/src/modules/Elsa.Telnyx/Models/TelnyxWebhook.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Elsa.Telnyx.Models; - -[Serializable] -public class TelnyxWebhook -{ - public TelnyxWebhookMeta Meta { get; set; } = default!; - public TelnyxWebhookData Data { get; set; } = default!; -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Models/TelnyxWebhookData.cs b/src/modules/Elsa.Telnyx/Models/TelnyxWebhookData.cs deleted file mode 100644 index 946cf3406..000000000 --- a/src/modules/Elsa.Telnyx/Models/TelnyxWebhookData.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Elsa.Telnyx.Models; - -// TODO: The Payload property is of type object instead of Payload, this in order for the JSON serializer to serialize derived type properties. -// Once moved to .NET 7, we have more control over polymorphism. -// https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/polymorphism?pivots=dotnet-7-0 -public record TelnyxWebhookData(string EventType, Guid Id, DateTimeOffset OccurredAt, string RecordType, object Payload) : TelnyxRecord(EventType); \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Models/TelnyxWebhookMeta.cs b/src/modules/Elsa.Telnyx/Models/TelnyxWebhookMeta.cs deleted file mode 100644 index 07f64ed40..000000000 --- a/src/modules/Elsa.Telnyx/Models/TelnyxWebhookMeta.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Elsa.Telnyx.Models; - -public class TelnyxWebhookMeta -{ - public int Attempt { get; set; } - public string DeliveredTo { get; set; } = default!; -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Options/TelnyxOptions.cs b/src/modules/Elsa.Telnyx/Options/TelnyxOptions.cs deleted file mode 100644 index d07e1a906..000000000 --- a/src/modules/Elsa.Telnyx/Options/TelnyxOptions.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Elsa.Telnyx.Options; - -public class TelnyxOptions -{ - public Uri ApiUrl { get; set; } = new("https://api.telnyx.com"); - public string ApiKey { get; set; } = default!; - public string? CallControlAppId { get; set; } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Payloads/Abstractions/CallPayload.cs b/src/modules/Elsa.Telnyx/Payloads/Abstractions/CallPayload.cs deleted file mode 100644 index 01d1de1a6..000000000 --- a/src/modules/Elsa.Telnyx/Payloads/Abstractions/CallPayload.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Elsa.Telnyx.Payloads.Abstractions; - -/// -/// A base class for payloads that are related to a call. -/// -public abstract record CallPayload : Payload -{ - public string CallControlId { get; init; } = default!; - public string CallLegId { get; init; } = default!; - public string CallSessionId { get; init; } = default!; - public string ConnectionId { get; init; } = default!; -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Payloads/Abstractions/CallPlayback.cs b/src/modules/Elsa.Telnyx/Payloads/Abstractions/CallPlayback.cs deleted file mode 100644 index b4664d2b2..000000000 --- a/src/modules/Elsa.Telnyx/Payloads/Abstractions/CallPlayback.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Elsa.Telnyx.Payloads.Abstractions; - -public abstract record CallPlayback : CallPayload -{ - public Uri MediaUrl { get; init; } = default!; - public bool Overlay { get; set; } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Payloads/Abstractions/Payload.cs b/src/modules/Elsa.Telnyx/Payloads/Abstractions/Payload.cs deleted file mode 100644 index 4a6b18cba..000000000 --- a/src/modules/Elsa.Telnyx/Payloads/Abstractions/Payload.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Elsa.Telnyx.Payloads.Abstractions; - -public abstract record Payload -{ - public string? ClientState { get; set; } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Payloads/Call/CallAnsweredPayload.cs b/src/modules/Elsa.Telnyx/Payloads/Call/CallAnsweredPayload.cs deleted file mode 100644 index f225fea20..000000000 --- a/src/modules/Elsa.Telnyx/Payloads/Call/CallAnsweredPayload.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Elsa.Telnyx.Attributes; -using Elsa.Telnyx.Payloads.Abstractions; - -namespace Elsa.Telnyx.Payloads.Call; - -/// -/// Represents the payload of a webhook event that is triggered when an incoming call is answered. -/// -[Webhook(WebhookEventTypes.CallAnswered)] -public sealed record CallAnsweredPayload : CallPayload -{ - /// - /// The from number. - /// - public string From { get; init; } = default!; - - /// - /// The to number. - /// - public string To { get; init; } = default!; - - /// - /// Call state. - /// - public string State { get; init; } = default!; -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Payloads/Call/CallBridgedPayload.cs b/src/modules/Elsa.Telnyx/Payloads/Call/CallBridgedPayload.cs deleted file mode 100644 index a3a319079..000000000 --- a/src/modules/Elsa.Telnyx/Payloads/Call/CallBridgedPayload.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Elsa.Telnyx.Attributes; -using Elsa.Telnyx.Payloads.Abstractions; - -namespace Elsa.Telnyx.Payloads.Call; - -[WebhookActivity(WebhookEventTypes.CallBridged, WebhookActivityTypeNames.CallBridged, "Call Bridged", "Triggered when an a call is bridged.")] -public sealed record CallBridgedPayload : CallPayload -{ - public string From { get; init; } = default!; - public string To { get; init; } = default!; - public string State { get; init; } = default!; -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Payloads/Call/CallDtmfReceivedPayload.cs b/src/modules/Elsa.Telnyx/Payloads/Call/CallDtmfReceivedPayload.cs deleted file mode 100644 index 26c266e0f..000000000 --- a/src/modules/Elsa.Telnyx/Payloads/Call/CallDtmfReceivedPayload.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Elsa.Telnyx.Attributes; -using Elsa.Telnyx.Payloads.Abstractions; - -namespace Elsa.Telnyx.Payloads.Call; - -[WebhookActivity(WebhookEventTypes.CallDtmfReceived, WebhookActivityTypeNames.CallDtmfReceived, "Call DTMF Received", "Triggered when DTMF input is received.")] -public sealed record CallDtmfReceivedPayload : CallPayload -{ - public string Digit { get; set; } = default!; - public string From { get; set; } = default!; - public string To { get; set; } = default!; -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Payloads/Call/CallGatherEndedPayload.cs b/src/modules/Elsa.Telnyx/Payloads/Call/CallGatherEndedPayload.cs deleted file mode 100644 index b8a9e149a..000000000 --- a/src/modules/Elsa.Telnyx/Payloads/Call/CallGatherEndedPayload.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Elsa.Telnyx.Attributes; -using Elsa.Telnyx.Payloads.Abstractions; - -namespace Elsa.Telnyx.Payloads.Call; - -[WebhookActivity(WebhookEventTypes.CallGatherEnded, WebhookActivityTypeNames.CallGatherEnded, "Call Gather Ended", "Triggered when an call gather has ended.")] -public sealed record CallGatherEndedPayload : CallPayload -{ - public string Digits { get; set; } = default!; - public string From { get; set; } = default!; - public string To { get; set; } = default!; - public string Status { get; set; } = default!; -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Payloads/Call/CallHangupPayload.cs b/src/modules/Elsa.Telnyx/Payloads/Call/CallHangupPayload.cs deleted file mode 100644 index 16af81fb8..000000000 --- a/src/modules/Elsa.Telnyx/Payloads/Call/CallHangupPayload.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Elsa.Telnyx.Attributes; -using Elsa.Telnyx.Payloads.Abstractions; - -namespace Elsa.Telnyx.Payloads.Call; - -/// -/// A payload representing the call.hangup Telnyx webhook event. -/// -[Webhook(WebhookEventTypes.CallHangup)] -public sealed record CallHangupPayload : CallPayload -{ - public DateTimeOffset StartTime { get; init; } - public DateTimeOffset EndTime { get; init; } - public string SipHangupCause { get; init; } = default!; - public string HangupSource { get; init; } = default!; - public string HangupCause { get; init; } = default!; - public string From { get; set; } = default!; - public string To { get; set; } = default!; -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Payloads/Call/CallInitiatedPayload.cs b/src/modules/Elsa.Telnyx/Payloads/Call/CallInitiatedPayload.cs deleted file mode 100644 index f58bb4bb8..000000000 --- a/src/modules/Elsa.Telnyx/Payloads/Call/CallInitiatedPayload.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Elsa.Telnyx.Attributes; -using Elsa.Telnyx.Payloads.Abstractions; - -namespace Elsa.Telnyx.Payloads.Call; - -[Webhook(WebhookEventTypes.CallInitiated)] -public sealed record CallInitiatedPayload : CallPayload -{ - public string Direction { get; init; } = default!; - public string State { get; init; } = default!; - public string To { get; init; } = default!; - public string From { get; init; } = default!; - public DateTimeOffset StartTime { get; set; } = default!; -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Payloads/Call/CallMachineDetectionEndedBase.cs b/src/modules/Elsa.Telnyx/Payloads/Call/CallMachineDetectionEndedBase.cs deleted file mode 100644 index 2d4110cf8..000000000 --- a/src/modules/Elsa.Telnyx/Payloads/Call/CallMachineDetectionEndedBase.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Elsa.Telnyx.Payloads.Abstractions; - -namespace Elsa.Telnyx.Payloads.Call; - -public record CallMachineDetectionEndedBase : CallPayload -{ - public string Result { get; set; } = default!; -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Payloads/Call/CallMachineGreetingEnded.cs b/src/modules/Elsa.Telnyx/Payloads/Call/CallMachineGreetingEnded.cs deleted file mode 100644 index 4d8bdd412..000000000 --- a/src/modules/Elsa.Telnyx/Payloads/Call/CallMachineGreetingEnded.cs +++ /dev/null @@ -1,6 +0,0 @@ -using Elsa.Telnyx.Attributes; - -namespace Elsa.Telnyx.Payloads.Call; - -[WebhookActivity(WebhookEventTypes.CallMachineGreetingEnded, WebhookActivityTypeNames.CallMachineGreetingEnded, "Call Machine Greeting Ended", "Triggered when a machine greeting has ended.")] -public sealed record CallMachineGreetingEnded : CallMachineGreetingEndedBase; \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Payloads/Call/CallMachineGreetingEndedBase.cs b/src/modules/Elsa.Telnyx/Payloads/Call/CallMachineGreetingEndedBase.cs deleted file mode 100644 index 7537c5a07..000000000 --- a/src/modules/Elsa.Telnyx/Payloads/Call/CallMachineGreetingEndedBase.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Elsa.Telnyx.Payloads.Abstractions; - -namespace Elsa.Telnyx.Payloads.Call; - -public record CallMachineGreetingEndedBase : CallPayload -{ - public string Result { get; set; } = default!; -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Payloads/Call/CallMachinePremiumDetectionEnded.cs b/src/modules/Elsa.Telnyx/Payloads/Call/CallMachinePremiumDetectionEnded.cs deleted file mode 100644 index 6ef5ea9c0..000000000 --- a/src/modules/Elsa.Telnyx/Payloads/Call/CallMachinePremiumDetectionEnded.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Elsa.Telnyx.Attributes; - -namespace Elsa.Telnyx.Payloads.Call; - -[WebhookActivity( - WebhookEventTypes.CallMachinePremiumDetectionEnded, - WebhookActivityTypeNames.CallMachinePremiumDetectionEnded, - "Call Machine Premium Detection Ended", - "Triggered when machine detection has ended." -)] -public sealed record CallMachinePremiumDetectionEnded : CallMachineDetectionEndedBase -{ -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Payloads/Call/CallMachinePremiumGreetingEnded.cs b/src/modules/Elsa.Telnyx/Payloads/Call/CallMachinePremiumGreetingEnded.cs deleted file mode 100644 index e8fdd3a9c..000000000 --- a/src/modules/Elsa.Telnyx/Payloads/Call/CallMachinePremiumGreetingEnded.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Elsa.Telnyx.Attributes; - -namespace Elsa.Telnyx.Payloads.Call; - -[WebhookActivity( - WebhookEventTypes.CallMachinePremiumGreetingEnded, - WebhookActivityTypeNames.CallMachinePremiumGreetingEnded, - "Call Machine Premium Greeting Ended", - "Triggered when a machine greeting has ended." -)] -public sealed record CallMachinePremiumGreetingEnded : CallMachineGreetingEndedBase; \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Payloads/Call/CallPlaybackEndedPayload.cs b/src/modules/Elsa.Telnyx/Payloads/Call/CallPlaybackEndedPayload.cs deleted file mode 100644 index f62c24020..000000000 --- a/src/modules/Elsa.Telnyx/Payloads/Call/CallPlaybackEndedPayload.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Elsa.Telnyx.Attributes; -using Elsa.Telnyx.Payloads.Abstractions; - -namespace Elsa.Telnyx.Payloads.Call; - -[WebhookActivity(WebhookEventTypes.CallPlaybackEnded, WebhookActivityTypeNames.CallPlaybackEnded, "Call Playback Ended", "Triggered when an audio playback has ended.")] -public sealed record CallPlaybackEndedPayload : CallPlayback -{ - public string Status { get; set; } = default!; -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Payloads/Call/CallPlaybackStartedPayload.cs b/src/modules/Elsa.Telnyx/Payloads/Call/CallPlaybackStartedPayload.cs deleted file mode 100644 index 67d1d5d37..000000000 --- a/src/modules/Elsa.Telnyx/Payloads/Call/CallPlaybackStartedPayload.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Elsa.Telnyx.Attributes; -using Elsa.Telnyx.Payloads.Abstractions; - -namespace Elsa.Telnyx.Payloads.Call; - -[WebhookActivity(WebhookEventTypes.CallPlaybackStarted, WebhookActivityTypeNames.CallPlaybackStarted, "Call Playback Started", "Triggered when an audio playback has started.")] -public sealed record CallPlaybackStartedPayload : CallPlayback; \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Payloads/Call/CallRecordingSavedPayload.cs b/src/modules/Elsa.Telnyx/Payloads/Call/CallRecordingSavedPayload.cs deleted file mode 100644 index d3502b9ba..000000000 --- a/src/modules/Elsa.Telnyx/Payloads/Call/CallRecordingSavedPayload.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Elsa.Telnyx.Attributes; -using Elsa.Telnyx.Models; -using Elsa.Telnyx.Payloads.Abstractions; - -namespace Elsa.Telnyx.Payloads.Call; - -[WebhookActivity(WebhookEventTypes.CallRecordingSaved, WebhookActivityTypeNames.CallRecordingSaved, "Call Recording Saved", "Triggered when a recording has been saved.")] -public sealed record CallRecordingSavedPayload : CallPayload -{ - public string Channels { get; set; } = default!; - public CallRecordingUrls PublicRecordingUrls { get; set; } = default!; - public CallRecordingUrls RecordingUrls { get; set; } = default!; - public DateTimeOffset RecordingEndedAt { get; set; } - public DateTimeOffset RecordingStartedAt { get; set; } - public TimeSpan Duration => RecordingEndedAt - RecordingStartedAt; -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Payloads/Call/CallSpeakEnded.cs b/src/modules/Elsa.Telnyx/Payloads/Call/CallSpeakEnded.cs deleted file mode 100644 index ab813a4a1..000000000 --- a/src/modules/Elsa.Telnyx/Payloads/Call/CallSpeakEnded.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Elsa.Telnyx.Attributes; -using Elsa.Telnyx.Payloads.Abstractions; - -namespace Elsa.Telnyx.Payloads.Call; - -[Webhook(WebhookEventTypes.CallSpeakEnded)] -public sealed record CallSpeakEnded : CallPayload; \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Payloads/Call/CallSpeakStarted.cs b/src/modules/Elsa.Telnyx/Payloads/Call/CallSpeakStarted.cs deleted file mode 100644 index d018a637f..000000000 --- a/src/modules/Elsa.Telnyx/Payloads/Call/CallSpeakStarted.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Elsa.Telnyx.Attributes; -using Elsa.Telnyx.Payloads.Abstractions; - -namespace Elsa.Telnyx.Payloads.Call; - -[Webhook(WebhookEventTypes.CallSpeakStarted)] -public sealed record CallSpeakStarted : CallPayload; \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Payloads/UnsupportedPayload.cs b/src/modules/Elsa.Telnyx/Payloads/UnsupportedPayload.cs deleted file mode 100644 index 888728996..000000000 --- a/src/modules/Elsa.Telnyx/Payloads/UnsupportedPayload.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.ComponentModel; -using Elsa.Telnyx.Payloads.Abstractions; - -namespace Elsa.Telnyx.Payloads; - -[Browsable(false)] -public sealed record UnsupportedPayload : Payload -{ -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Providers/WebhookEventActivityProvider.cs b/src/modules/Elsa.Telnyx/Providers/WebhookEventActivityProvider.cs deleted file mode 100644 index 0ea0e6f29..000000000 --- a/src/modules/Elsa.Telnyx/Providers/WebhookEventActivityProvider.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System.ComponentModel; -using System.Reflection; -using Elsa.Extensions; -using Elsa.Telnyx.Activities; -using Elsa.Telnyx.Attributes; -using Elsa.Telnyx.Helpers; -using Elsa.Telnyx.Payloads.Abstractions; -using Elsa.Workflows; -using Elsa.Workflows.Management; -using Elsa.Workflows.Models; - -namespace Elsa.Telnyx.Providers; - -/// -/// Provides activity descriptors based on Telnyx webhook event payload types (types inheriting . -/// -public class WebhookEventActivityProvider : IActivityProvider -{ - private readonly IActivityFactory _activityFactory; - private readonly IActivityDescriber _activityDescriber; - - /// - /// Constructor. - /// - public WebhookEventActivityProvider(IActivityFactory activityFactory, IActivityDescriber activityDescriber) - { - _activityFactory = activityFactory; - _activityDescriber = activityDescriber; - } - - - /// - public async ValueTask> GetDescriptorsAsync(CancellationToken cancellationToken = default) - { - var payloadTypes = WebhookPayloadTypes.PayloadTypes.Where(x => x.GetCustomAttribute() != null); - return await CreateDescriptorsAsync(payloadTypes, cancellationToken); - } - - private async Task> CreateDescriptorsAsync(IEnumerable payloadTypes, CancellationToken cancellationToken = default) - { - return await Task.WhenAll(payloadTypes.Select(async x => await CreateDescriptorAsync(x, cancellationToken))); - } - - private async Task CreateDescriptorAsync(Type payloadType, CancellationToken cancellationToken = default) - { - var webhookAttribute = payloadType.GetCustomAttribute() ?? throw new Exception($"No WebhookActivityAttribute found on payload type {payloadType}"); - var typeName = webhookAttribute.ActivityType; - var displayNameAttr = payloadType.GetCustomAttribute(); - var displayName = displayNameAttr?.DisplayName ?? webhookAttribute.DisplayName; - var categoryAttr = payloadType.GetCustomAttribute(); - var category = categoryAttr?.Category ?? Constants.Category; - var descriptionAttr = payloadType.GetCustomAttribute(); - var description = descriptionAttr?.Description ?? webhookAttribute.Description; - var outputPropertyDescriptor = await _activityDescriber.DescribeOutputProperty>(x => x.Result!, cancellationToken); - - outputPropertyDescriptor.Type = payloadType; - - return new() - { - TypeName = typeName, - Name = typeName, - Version = 1, - DisplayName = displayName, - Description = description, - Category = category, - Kind = ActivityKind.Trigger, - IsBrowsable = true, - Attributes = { webhookAttribute! }, - Outputs = { outputPropertyDescriptor }, - Constructor = context => - { - var activity = _activityFactory.Create(context); - activity.Type = typeName; - activity.EventType = webhookAttribute!.EventType; - - return activity; - } - }; - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Serialization/SnakeCaseNamingPolicy.cs b/src/modules/Elsa.Telnyx/Serialization/SnakeCaseNamingPolicy.cs deleted file mode 100644 index a0f5d296e..000000000 --- a/src/modules/Elsa.Telnyx/Serialization/SnakeCaseNamingPolicy.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Text.Json; -using Humanizer; - -namespace Elsa.Telnyx.Serialization; - -/// -/// Reads and writes names using snake_case casing. -/// -public sealed class SnakeCaseNamingPolicy : JsonNamingPolicy -{ - /// - public override string ConvertName(string name) => name.Underscore(); -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Serialization/WebhookDataJsonConverter.cs b/src/modules/Elsa.Telnyx/Serialization/WebhookDataJsonConverter.cs deleted file mode 100644 index b25fbdace..000000000 --- a/src/modules/Elsa.Telnyx/Serialization/WebhookDataJsonConverter.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; -using Elsa.Extensions; -using Elsa.Telnyx.Helpers; -using Elsa.Telnyx.Models; -using Elsa.Telnyx.Payloads.Abstractions; - -namespace Elsa.Telnyx.Serialization; - -/// -/// Converts json payloads received from Telnyx into concrete objects. -/// -public class WebhookDataJsonConverter : JsonConverter -{ - /// - public override TelnyxWebhookData Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - var dataModel = JsonElement.ParseValue(ref reader); - var eventType = dataModel.GetProperty("event_type", "EventType").GetString()!; - var id = dataModel.GetProperty("id", "Id").GetGuid(); - var occurredAt = dataModel.GetProperty("occurred_at", "OccurredAt").GetDateTimeOffset(); - var recordType = dataModel.GetProperty("record_type", "RecordType").GetString()!; - var payload = PayloadSerializer.Deserialize(eventType, dataModel.GetProperty("payload", "Payload"), options); - - return new TelnyxWebhookData(eventType, id, occurredAt, recordType, payload); - } - - /// - public override void Write(Utf8JsonWriter writer, TelnyxWebhookData value, JsonSerializerOptions options) - { - var localOptions = new JsonSerializerOptions - { - PropertyNamingPolicy = new SnakeCaseNamingPolicy() - }; - - var model = JsonSerializer.SerializeToElement(value); - JsonSerializer.Serialize(writer, model, localOptions); - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/Services/WebhookHandler.cs b/src/modules/Elsa.Telnyx/Services/WebhookHandler.cs deleted file mode 100644 index c74495a2f..000000000 --- a/src/modules/Elsa.Telnyx/Services/WebhookHandler.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Text; -using System.Text.Json; -using Elsa.Mediator; -using Elsa.Mediator.Contracts; -using Elsa.Telnyx.Contracts; -using Elsa.Telnyx.Events; -using Elsa.Telnyx.Models; -using Elsa.Telnyx.Serialization; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; - -namespace Elsa.Telnyx.Services; - -internal class WebhookHandler(INotificationSender notificationSender, ILogger logger) : IWebhookHandler -{ - private static readonly JsonSerializerOptions SerializerSettings; - - static WebhookHandler() - { - SerializerSettings = CreateSerializerSettings(); - } - - public async Task HandleAsync(HttpContext httpContext) - { - var cancellationToken = httpContext.RequestAborted; - var json = await ReadRequestBodyAsync(httpContext); - var webhook = JsonSerializer.Deserialize(json, SerializerSettings)!; - - logger.LogDebug("Telnyx webhook payload received: {@Webhook}", webhook); - await notificationSender.SendAsync(new TelnyxWebhookReceived(webhook), NotificationStrategy.Background, cancellationToken); - } - - private static async Task ReadRequestBodyAsync(HttpContext httpContext) - { - string body; - var req = httpContext.Request; - - // Allows using several time the stream in ASP.Net Core - req.EnableBuffering(); - - // Arguments: Stream, Encoding, detect encoding, buffer size AND, the most important: keep stream opened. - using (var reader = new StreamReader(req.Body, Encoding.UTF8, true, 1024, true)) - body = await reader.ReadToEndAsync(); - - // Rewind, so the core is not lost when it looks at the body for the request - req.Body.Position = 0; - return body; - } - - private static JsonSerializerOptions CreateSerializerSettings() - { - var settings = new JsonSerializerOptions - { - PropertyNamingPolicy = new SnakeCaseNamingPolicy() - }; - - settings.Converters.Add(new WebhookDataJsonConverter()); - return settings; - } -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/WebhookActivityTypeNames.cs b/src/modules/Elsa.Telnyx/WebhookActivityTypeNames.cs deleted file mode 100644 index a17cd5ccc..000000000 --- a/src/modules/Elsa.Telnyx/WebhookActivityTypeNames.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Elsa.Telnyx; - -public static class WebhookActivityTypeNames -{ - public const string CallBridged = $"{Constants.Namespace}.{nameof(CallBridged)}"; - public const string CallDtmfReceived = $"{Constants.Namespace}.{nameof(CallDtmfReceived)}"; - public const string CallGatherEnded = $"{Constants.Namespace}.{nameof(CallGatherEnded)}"; - public const string CallHangup = $"{Constants.Namespace}.{nameof(CallHangup)}"; - public const string CallInitiated = $"{Constants.Namespace}.{nameof(CallInitiated)}"; - public const string CallMachineGreetingEnded = $"{Constants.Namespace}.{nameof(CallMachineGreetingEnded)}"; - public const string CallMachinePremiumGreetingEnded = $"{Constants.Namespace}.{nameof(CallMachinePremiumGreetingEnded)}"; - public const string CallMachineDetectionEnded = $"{Constants.Namespace}.{nameof(CallMachineDetectionEnded)}"; - public const string CallMachinePremiumDetectionEnded = $"{Constants.Namespace}.{nameof(CallMachinePremiumDetectionEnded)}"; - public const string CallPlaybackStarted = $"{Constants.Namespace}.{nameof(CallPlaybackStarted)}"; - public const string CallPlaybackEnded = $"{Constants.Namespace}.{nameof(CallPlaybackEnded)}"; - public const string CallRecordingSaved = $"{Constants.Namespace}.{nameof(CallRecordingSaved)}"; - public const string CallSpeakStarted = $"{Constants.Namespace}.{nameof(CallSpeakStarted)}"; - public const string CallSpeakEnded = $"{Constants.Namespace}.{nameof(CallSpeakEnded)}"; -} \ No newline at end of file diff --git a/src/modules/Elsa.Telnyx/WebhookEventTypes.cs b/src/modules/Elsa.Telnyx/WebhookEventTypes.cs deleted file mode 100644 index c45c5d939..000000000 --- a/src/modules/Elsa.Telnyx/WebhookEventTypes.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Elsa.Telnyx; - -public static class WebhookEventTypes -{ - public const string CallAnswered = "call.answered"; - public const string CallBridged = "call.bridged"; - public const string CallDtmfReceived = "call.dtmf.received"; - public const string CallGatherEnded = "call.gather.ended"; - public const string CallHangup = "call.hangup"; - public const string CallInitiated = "call.initiated"; - public const string CallMachineGreetingEnded = "call.machine.greeting.ended"; - public const string CallMachinePremiumGreetingEnded = "call.machine.premium.greeting.ended"; - public const string CallMachineDetectionEnded = "call.machine.detection.ended"; - public const string CallMachinePremiumDetectionEnded = "call.machine.premium.detection.ended"; - public const string CallPlaybackStarted = "call.playback.started"; - public const string CallPlaybackEnded = "call.playback.ended"; - public const string CallRecordingSaved = "call.recording.saved"; - public const string CallSpeakStarted = "call.speak.started"; - public const string CallSpeakEnded = "call.speak.ended"; -} \ No newline at end of file