Überführung von github
This commit is contained in:
stefanpflug 2025-03-31 08:42:12 +02:00
parent 4f4eabb53c
commit 34ab7a7176
94 changed files with 4518 additions and 1 deletions

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<Graphical_Application_Designer_Project>
<Version>2.1</Version>
<Files>
<File path="Main.flow" type="callflow" />
</Files>
<Variables>
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Variable>
<Name>ExtensionNr</Name>
<ShowScopeProperty>false</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
<Variable>
<Name>AuswahlOpt</Name>
<ShowScopeProperty>false</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
<Variable>
<Name>Zielstatus</Name>
<ShowScopeProperty>false</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
</ArrayOfVariable>
</Variables>
<DebugBuildSuccessful>False</DebugBuildSuccessful>
<ReleaseBuildSuccessful>True</ReleaseBuildSuccessful>
<DebugBuildNumber>0</DebugBuildNumber>
<ReleaseBuildNumber>51</ReleaseBuildNumber>
<ChangedSinceLastDebugBuild>True</ChangedSinceLastDebugBuild>
<DoNotAskForExtension>False</DoNotAskForExtension>
<Extension>*39</Extension>
<OnlineServicesTextToSpeechEngine>None</OnlineServicesTextToSpeechEngine>
<OnlineServicesSpeechToTextEngine>None</OnlineServicesSpeechToTextEngine>
<AmazonClientID>
</AmazonClientID>
<AmazonClientSecret>
</AmazonClientSecret>
<AmazonRegion>us-east-2</AmazonRegion>
<AmazonLexicons>
</AmazonLexicons>
<GoogleCloudServiceAccountKeyFileName>
</GoogleCloudServiceAccountKeyFileName>
<GoogleCloudServiceAccountKeyJSON>
</GoogleCloudServiceAccountKeyJSON>
</Graphical_Application_Designer_Project>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<File>
<Version>2.1</Version>
<Variables>
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
</Variables>
<Flows>
<MainFlow>
<ns0:MainFlow Description="Callflow execution path." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e">
<ns0:UserInputComponent AcceptDtmfInput="True" FinalDigitTimeout="2" StopDigit="DigitPound" IsValidDigit_3="True" IsValidDigit_0="True" IsValidDigit_1="True" IsValidDigit_Pound="False" InvalidDigitPromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;FehlerBeiDerEingabe-vicky.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;" IsValidDigit_7="True" IsValidDigit_4="True" IsValidDigit_5="True" SubsequentPromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;leer50ms.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;" IsValidDigit_2="True" InterDigitTimeout="3" IsValidDigit_6="True" MinDigits="1" InitialPromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;welcheNSt.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;" x:Name="InputExtension" MaxRetryCount="3" DebugModeActive="False" FirstDigitTimeout="5" MaxDigits="14" Tag="" IsValidDigit_8="True" IsValidDigit_9="True" IsValidDigit_Star="False" TimeoutPromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;">
<ns0:ComponentBranch DisplayedText="Valid Input" Description="Execution path when the specified branch is activated." Tag="" DebugModeActive="False" x:Name="componentBranch1" />
<ns0:ComponentBranch DisplayedText="Invalid Input" Description="Execution path when the specified branch is activated." Tag="" DebugModeActive="False" x:Name="componentBranch2" />
</ns0:UserInputComponent>
<ns0:VariableAssignmentComponent VariableName="project$.ExtensionNr" Tag="" DebugModeActive="False" Expression="InputExtension.Buffer" x:Name="variableAssignmentExtensionNr" />
<ns0:UserInputComponent AcceptDtmfInput="True" FinalDigitTimeout="2" StopDigit="DigitPound" IsValidDigit_3="True" IsValidDigit_0="True" IsValidDigit_1="True" IsValidDigit_Pound="False" InvalidDigitPromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;FehlerBeiDerEingabe-vicky.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;" IsValidDigit_7="False" IsValidDigit_4="True" IsValidDigit_5="False" SubsequentPromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;leer50ms.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;" IsValidDigit_2="True" InterDigitTimeout="3" IsValidDigit_6="False" MinDigits="1" InitialPromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;welcher_Status.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;" x:Name="InputStatus" MaxRetryCount="3" DebugModeActive="False" FirstDigitTimeout="5" MaxDigits="1" Tag="" IsValidDigit_8="False" IsValidDigit_9="False" IsValidDigit_Star="False" TimeoutPromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;">
<ns0:ComponentBranch DisplayedText="Valid Input" Description="Execution path when the specified branch is activated." Tag="" DebugModeActive="False" x:Name="componentBranch3" />
<ns0:ComponentBranch DisplayedText="Invalid Input" Description="Execution path when the specified branch is activated." Tag="" DebugModeActive="False" x:Name="componentBranch4" />
</ns0:UserInputComponent>
<ns0:VariableAssignmentComponent VariableName="project$.Zielstatus" Tag="" DebugModeActive="False" Expression="InputStatus.Buffer" x:Name="variableAssignmentZielStatus" />
<ns0:ConditionalComponent Tag="" DebugModeActive="False" x:Name="CreateCondition1">
<ns0:ConditionalComponentBranch Condition="EQUAL(project$.Zielstatus,0)" Description="Execution path when the specified condition is met." Tag="" DebugModeActive="False" x:Name="conditionalComponentBranch1">
<ns0:TcxSetExtensionStatusComponent Status="Available" Tag="" DebugModeActive="False" Extension="project$.ExtensionNr" x:Name="SetExtensionStatus_Available" />
<ns0:PromptPlaybackComponent Tag="" AcceptDtmfInput="True" DebugModeActive="False" PromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;Status0.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;" x:Name="PromptPlayback1" />
</ns0:ConditionalComponentBranch>
<ns0:ConditionalComponentBranch Condition="EQUAL(project$.Zielstatus,1)" Description="Execution path when the specified condition is met." Tag="" DebugModeActive="False" x:Name="conditionalComponentBranch2">
<ns0:TcxSetExtensionStatusComponent Status="Away" Tag="" DebugModeActive="False" Extension="project$.ExtensionNr" x:Name="SetExtensionStatus_Away" />
<ns0:PromptPlaybackComponent Tag="" AcceptDtmfInput="True" DebugModeActive="False" PromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;Status1.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;" x:Name="promptPlaybackComponent1" />
</ns0:ConditionalComponentBranch>
<ns0:ConditionalComponentBranch Condition="EQUAL(project$.Zielstatus,2)" Description="Execution path when the specified condition is met." Tag="" DebugModeActive="False" x:Name="conditionalComponentBranch3">
<ns0:TcxSetExtensionStatusComponent Status="DoNotDisturb" Tag="" DebugModeActive="False" Extension="project$.ExtensionNr" x:Name="SetExtensionStatus_DND" />
<ns0:PromptPlaybackComponent Tag="" AcceptDtmfInput="True" DebugModeActive="False" PromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;Sttatus2.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;" x:Name="promptPlaybackComponent2" />
</ns0:ConditionalComponentBranch>
<ns0:ConditionalComponentBranch Condition="EQUAL(project$.Zielstatus,3)" Description="Execution path when the specified condition is met." Tag="" DebugModeActive="False" x:Name="conditionalComponentBranch4">
<ns0:TcxSetExtensionStatusComponent Status="Lunch" Tag="" DebugModeActive="False" Extension="project$.ExtensionNr" x:Name="SetExtensionStatus_Custom1" />
<ns0:PromptPlaybackComponent Tag="" AcceptDtmfInput="True" DebugModeActive="False" PromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;Status3.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;" x:Name="promptPlaybackComponent3" />
</ns0:ConditionalComponentBranch>
<ns0:ConditionalComponentBranch Condition="EQUAL(project$.Zielstatus,4)" Description="Execution path when the specified condition is met." Tag="" DebugModeActive="False" x:Name="conditionalComponentBranch5">
<ns0:TcxSetExtensionStatusComponent Status="BusinessTrip" Description="Custom State 2" Tag="" DebugModeActive="False" Extension="project$.ExtensionNr" x:Name="SetExtensionStatus_Custom2" />
<ns0:PromptPlaybackComponent Tag="" AcceptDtmfInput="True" DebugModeActive="False" PromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;Status4.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;" x:Name="promptPlaybackComponent4" />
</ns0:ConditionalComponentBranch>
</ns0:ConditionalComponent>
</ns0:MainFlow>
</MainFlow>
<ErrorHandlerFlow>
<ns0:ErrorHandlerFlow Description="Execution path when an error ocurrs." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
</ErrorHandlerFlow>
<DisconnectHandlerFlow>
<ns0:DisconnectHandlerFlow Description="Execution path since the call gets disconnected." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
</DisconnectHandlerFlow>
</Flows>
</File>

View File

@ -0,0 +1,570 @@
using CallFlow.CFD;
using CallFlow;
using MimeKit;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks.Dataflow;
using System.Threading.Tasks;
using System.Threading;
using System;
using TCX.Configuration;
namespace _39_Profilstatus_mitExtension
{
public class Main : ScriptBase<Main>, ICallflow, ICallflowProcessor
{
private bool executionStarted;
private bool executionFinished;
private bool disconnectFlowPending;
private BufferBlock<AbsEvent> eventBuffer;
private int currentComponentIndex;
private List<AbsComponent> mainFlowComponentList;
private List<AbsComponent> disconnectFlowComponentList;
private List<AbsComponent> errorFlowComponentList;
private List<AbsComponent> currentFlowComponentList;
private LogFormatter logFormatter;
private TimerManager timerManager;
private Dictionary<string, Variable> variableMap;
private TempWavFileManager tempWavFileManager;
private PromptQueue promptQueue;
private OnlineServices onlineServices;
private OfficeHoursManager officeHoursManager;
private CfdAppScope scope;
private void DisconnectCallAndExitCallflow()
{
if (currentFlowComponentList == disconnectFlowComponentList)
logFormatter.Trace("Callflow finished...");
else
{
logFormatter.Trace("Callflow finished, disconnecting call...");
MyCall.Terminate();
}
}
private async Task ExecuteErrorFlow()
{
if (currentFlowComponentList == errorFlowComponentList)
{
logFormatter.Trace("Error during error handler flow, exiting callflow...");
DisconnectCallAndExitCallflow();
}
else if (currentFlowComponentList == disconnectFlowComponentList)
{
logFormatter.Trace("Error during disconnect handler flow, exiting callflow...");
executionFinished = true;
}
else
{
currentFlowComponentList = errorFlowComponentList;
currentComponentIndex = 0;
if (errorFlowComponentList.Count > 0)
{
logFormatter.Trace("Start executing error handler flow...");
await ProcessStart();
}
else
{
logFormatter.Trace("Error handler flow is empty...");
DisconnectCallAndExitCallflow();
}
}
}
private async Task ExecuteDisconnectFlow()
{
currentFlowComponentList = disconnectFlowComponentList;
currentComponentIndex = 0;
disconnectFlowPending = false;
if (disconnectFlowComponentList.Count > 0)
{
logFormatter.Trace("Start executing disconnect handler flow...");
await ProcessStart();
}
else
{
logFormatter.Trace("Disconnect handler flow is empty...");
executionFinished = true;
}
}
private EventResults CheckEventResult(EventResults eventResult)
{
if (eventResult == EventResults.MoveToNextComponent && ++currentComponentIndex == currentFlowComponentList.Count)
{
DisconnectCallAndExitCallflow();
return EventResults.Exit;
}
else if (eventResult == EventResults.Exit)
DisconnectCallAndExitCallflow();
return eventResult;
}
private void InitializeVariables(string callID)
{
// Call variables
variableMap["session.ani"] = new Variable(MyCall.Caller.CallerID);
variableMap["session.callid"] = new Variable(callID);
variableMap["session.dnis"] = new Variable(MyCall.DN.Number);
variableMap["session.did"] = new Variable(MyCall.Caller.CalledNumber);
variableMap["session.audioFolder"] = new Variable(Path.Combine(RecordingManager.Instance.AudioFolder, promptQueue.ProjectAudioFolder));
variableMap["session.transferingExtension"] = new Variable(MyCall.ReferredByDN?.Number ?? string.Empty);
variableMap["session.forwardingExtension"] = new Variable(MyCall.OnBehalfOf?.Number ?? string.Empty);
// Standard variables
variableMap["RecordResult.NothingRecorded"] = new Variable(RecordComponent.RecordResults.NothingRecorded);
variableMap["RecordResult.StopDigit"] = new Variable(RecordComponent.RecordResults.StopDigit);
variableMap["RecordResult.Completed"] = new Variable(RecordComponent.RecordResults.Completed);
variableMap["MenuResult.Timeout"] = new Variable(MenuComponent.MenuResults.Timeout);
variableMap["MenuResult.InvalidOption"] = new Variable(MenuComponent.MenuResults.InvalidOption);
variableMap["MenuResult.ValidOption"] = new Variable(MenuComponent.MenuResults.ValidOption);
variableMap["UserInputResult.Timeout"] = new Variable(UserInputComponent.UserInputResults.Timeout);
variableMap["UserInputResult.InvalidDigits"] = new Variable(UserInputComponent.UserInputResults.InvalidDigits);
variableMap["UserInputResult.ValidDigits"] = new Variable(UserInputComponent.UserInputResults.ValidDigits);
variableMap["VoiceInputResult.Timeout"] = new Variable(VoiceInputComponent.VoiceInputResults.Timeout);
variableMap["VoiceInputResult.InvalidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.InvalidInput);
variableMap["VoiceInputResult.ValidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidInput);
variableMap["VoiceInputResult.ValidDtmfInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidDtmfInput);
// User variables
variableMap["project$.ExtensionNr"] = new Variable("");
variableMap["project$.AuswahlOpt"] = new Variable("");
variableMap["project$.Zielstatus"] = new Variable("");
variableMap["RecordResult.NothingRecorded"] = new Variable(RecordComponent.RecordResults.NothingRecorded);
variableMap["RecordResult.StopDigit"] = new Variable(RecordComponent.RecordResults.StopDigit);
variableMap["RecordResult.Completed"] = new Variable(RecordComponent.RecordResults.Completed);
variableMap["MenuResult.Timeout"] = new Variable(MenuComponent.MenuResults.Timeout);
variableMap["MenuResult.InvalidOption"] = new Variable(MenuComponent.MenuResults.InvalidOption);
variableMap["MenuResult.ValidOption"] = new Variable(MenuComponent.MenuResults.ValidOption);
variableMap["UserInputResult.Timeout"] = new Variable(UserInputComponent.UserInputResults.Timeout);
variableMap["UserInputResult.InvalidDigits"] = new Variable(UserInputComponent.UserInputResults.InvalidDigits);
variableMap["UserInputResult.ValidDigits"] = new Variable(UserInputComponent.UserInputResults.ValidDigits);
variableMap["VoiceInputResult.Timeout"] = new Variable(VoiceInputComponent.VoiceInputResults.Timeout);
variableMap["VoiceInputResult.InvalidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.InvalidInput);
variableMap["VoiceInputResult.ValidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidInput);
variableMap["VoiceInputResult.ValidDtmfInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidDtmfInput);
}
private void InitializeComponents(ICallflow callflow, ICall myCall, string logHeader)
{
scope = CfdModule.Instance.CreateScope(callflow, myCall, logHeader);
{
UserInputComponent InputExtension = scope.CreateComponent<UserInputComponent>("InputExtension");
InputExtension.AllowDtmfInput = true;
InputExtension.MaxRetryCount = 2;
InputExtension.FirstDigitTimeout = 5000;
InputExtension.InterDigitTimeout = 3000;
InputExtension.FinalDigitTimeout = 2000;
InputExtension.MinDigits = 1;
InputExtension.MaxDigits = 14;
InputExtension.ValidDigitList.AddRange(new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' });
InputExtension.StopDigitList.AddRange(new char[] { '#' });
InputExtension.InitialPrompts.Add(new AudioFilePrompt(() => { return "welcheNSt.wav"; }));
InputExtension.SubsequentPrompts.Add(new AudioFilePrompt(() => { return "leer50ms.wav"; }));
InputExtension.InvalidDigitPrompts.Add(new AudioFilePrompt(() => { return "FehlerBeiDerEingabe-vicky.wav"; }));
InputExtension.TimeoutPrompts.Add(new AudioFilePrompt(() => { return "DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav"; }));
mainFlowComponentList.Add(InputExtension);
ConditionalComponent InputExtension_Conditional = scope.CreateComponent<ConditionalComponent>("InputExtension_Conditional");
mainFlowComponentList.Add(InputExtension_Conditional);
InputExtension_Conditional.ConditionList.Add(() => { return InputExtension.Result == UserInputComponent.UserInputResults.ValidDigits; });
InputExtension_Conditional.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("InputExtension_Conditional_ValidInput"));
InputExtension_Conditional.ConditionList.Add(() => { return InputExtension.Result == UserInputComponent.UserInputResults.InvalidDigits || InputExtension.Result == UserInputComponent.UserInputResults.Timeout; });
InputExtension_Conditional.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("InputExtension_Conditional_InvalidInput"));
VariableAssignmentComponent variableAssignmentExtensionNr = scope.CreateComponent<VariableAssignmentComponent>("variableAssignmentExtensionNr");
variableAssignmentExtensionNr.VariableName = "project$.ExtensionNr";
variableAssignmentExtensionNr.VariableValueHandler = () => { return InputExtension.Buffer; };
mainFlowComponentList.Add(variableAssignmentExtensionNr);
UserInputComponent InputStatus = scope.CreateComponent<UserInputComponent>("InputStatus");
InputStatus.AllowDtmfInput = true;
InputStatus.MaxRetryCount = 2;
InputStatus.FirstDigitTimeout = 5000;
InputStatus.InterDigitTimeout = 3000;
InputStatus.FinalDigitTimeout = 2000;
InputStatus.MinDigits = 1;
InputStatus.MaxDigits = 1;
InputStatus.ValidDigitList.AddRange(new char[] { '0', '1', '2', '3', '4' });
InputStatus.StopDigitList.AddRange(new char[] { '#' });
InputStatus.InitialPrompts.Add(new AudioFilePrompt(() => { return "welcher_Status.wav"; }));
InputStatus.SubsequentPrompts.Add(new AudioFilePrompt(() => { return "leer50ms.wav"; }));
InputStatus.InvalidDigitPrompts.Add(new AudioFilePrompt(() => { return "FehlerBeiDerEingabe-vicky.wav"; }));
InputStatus.TimeoutPrompts.Add(new AudioFilePrompt(() => { return "DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav"; }));
mainFlowComponentList.Add(InputStatus);
ConditionalComponent InputStatus_Conditional = scope.CreateComponent<ConditionalComponent>("InputStatus_Conditional");
mainFlowComponentList.Add(InputStatus_Conditional);
InputStatus_Conditional.ConditionList.Add(() => { return InputStatus.Result == UserInputComponent.UserInputResults.ValidDigits; });
InputStatus_Conditional.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("InputStatus_Conditional_ValidInput"));
InputStatus_Conditional.ConditionList.Add(() => { return InputStatus.Result == UserInputComponent.UserInputResults.InvalidDigits || InputStatus.Result == UserInputComponent.UserInputResults.Timeout; });
InputStatus_Conditional.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("InputStatus_Conditional_InvalidInput"));
VariableAssignmentComponent variableAssignmentZielStatus = scope.CreateComponent<VariableAssignmentComponent>("variableAssignmentZielStatus");
variableAssignmentZielStatus.VariableName = "project$.Zielstatus";
variableAssignmentZielStatus.VariableValueHandler = () => { return InputStatus.Buffer; };
mainFlowComponentList.Add(variableAssignmentZielStatus);
ConditionalComponent CreateCondition1 = scope.CreateComponent<ConditionalComponent>("CreateCondition1");
mainFlowComponentList.Add(CreateCondition1);
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["project$.Zielstatus"].Value,0)); });
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch1"));
TcxSetExtensionStatusComponent SetExtensionStatus_Available = scope.CreateComponent<TcxSetExtensionStatusComponent>("SetExtensionStatus_Available");
SetExtensionStatus_Available.ExtensionHandler = () => { return Convert.ToString(variableMap["project$.ExtensionNr"].Value); };
SetExtensionStatus_Available.ProfileNameHandler = () => { return "Available"; };
CreateCondition1.ContainerList[0].ComponentList.Add(SetExtensionStatus_Available);
PromptPlaybackComponent PromptPlayback1 = scope.CreateComponent<PromptPlaybackComponent>("PromptPlayback1");
PromptPlayback1.AllowDtmfInput = true;
PromptPlayback1.Prompts.Add(new AudioFilePrompt(() => { return "Status0.wav"; }));
CreateCondition1.ContainerList[0].ComponentList.Add(PromptPlayback1);
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["project$.Zielstatus"].Value,1)); });
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch2"));
TcxSetExtensionStatusComponent SetExtensionStatus_Away = scope.CreateComponent<TcxSetExtensionStatusComponent>("SetExtensionStatus_Away");
SetExtensionStatus_Away.ExtensionHandler = () => { return Convert.ToString(variableMap["project$.ExtensionNr"].Value); };
SetExtensionStatus_Away.ProfileNameHandler = () => { return "Away"; };
CreateCondition1.ContainerList[1].ComponentList.Add(SetExtensionStatus_Away);
PromptPlaybackComponent promptPlaybackComponent1 = scope.CreateComponent<PromptPlaybackComponent>("promptPlaybackComponent1");
promptPlaybackComponent1.AllowDtmfInput = true;
promptPlaybackComponent1.Prompts.Add(new AudioFilePrompt(() => { return "Status1.wav"; }));
CreateCondition1.ContainerList[1].ComponentList.Add(promptPlaybackComponent1);
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["project$.Zielstatus"].Value,2)); });
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch3"));
TcxSetExtensionStatusComponent SetExtensionStatus_DND = scope.CreateComponent<TcxSetExtensionStatusComponent>("SetExtensionStatus_DND");
SetExtensionStatus_DND.ExtensionHandler = () => { return Convert.ToString(variableMap["project$.ExtensionNr"].Value); };
SetExtensionStatus_DND.ProfileNameHandler = () => { return "Out of office"; };
CreateCondition1.ContainerList[2].ComponentList.Add(SetExtensionStatus_DND);
PromptPlaybackComponent promptPlaybackComponent2 = scope.CreateComponent<PromptPlaybackComponent>("promptPlaybackComponent2");
promptPlaybackComponent2.AllowDtmfInput = true;
promptPlaybackComponent2.Prompts.Add(new AudioFilePrompt(() => { return "Sttatus2.wav"; }));
CreateCondition1.ContainerList[2].ComponentList.Add(promptPlaybackComponent2);
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["project$.Zielstatus"].Value,3)); });
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch4"));
TcxSetExtensionStatusComponent SetExtensionStatus_Custom1 = scope.CreateComponent<TcxSetExtensionStatusComponent>("SetExtensionStatus_Custom1");
SetExtensionStatus_Custom1.ExtensionHandler = () => { return Convert.ToString(variableMap["project$.ExtensionNr"].Value); };
SetExtensionStatus_Custom1.ProfileNameHandler = () => { return "Custom 1"; };
CreateCondition1.ContainerList[3].ComponentList.Add(SetExtensionStatus_Custom1);
PromptPlaybackComponent promptPlaybackComponent3 = scope.CreateComponent<PromptPlaybackComponent>("promptPlaybackComponent3");
promptPlaybackComponent3.AllowDtmfInput = true;
promptPlaybackComponent3.Prompts.Add(new AudioFilePrompt(() => { return "Status3.wav"; }));
CreateCondition1.ContainerList[3].ComponentList.Add(promptPlaybackComponent3);
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["project$.Zielstatus"].Value,4)); });
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch5"));
TcxSetExtensionStatusComponent SetExtensionStatus_Custom2 = scope.CreateComponent<TcxSetExtensionStatusComponent>("SetExtensionStatus_Custom2");
SetExtensionStatus_Custom2.ExtensionHandler = () => { return Convert.ToString(variableMap["project$.ExtensionNr"].Value); };
SetExtensionStatus_Custom2.ProfileNameHandler = () => { return "Custom 2"; };
CreateCondition1.ContainerList[4].ComponentList.Add(SetExtensionStatus_Custom2);
PromptPlaybackComponent promptPlaybackComponent4 = scope.CreateComponent<PromptPlaybackComponent>("promptPlaybackComponent4");
promptPlaybackComponent4.AllowDtmfInput = true;
promptPlaybackComponent4.Prompts.Add(new AudioFilePrompt(() => { return "Status4.wav"; }));
CreateCondition1.ContainerList[4].ComponentList.Add(promptPlaybackComponent4);
}
{
}
{
}
// Add a final DisconnectCall component to the main and error handler flows, in order to complete pending prompt playbacks...
DisconnectCallComponent mainAutoAddedFinalDisconnectCall = scope.CreateComponent<DisconnectCallComponent>("mainAutoAddedFinalDisconnectCall");
DisconnectCallComponent errorHandlerAutoAddedFinalDisconnectCall = scope.CreateComponent<DisconnectCallComponent>("errorHandlerAutoAddedFinalDisconnectCall");
mainFlowComponentList.Add(mainAutoAddedFinalDisconnectCall);
errorFlowComponentList.Add(errorHandlerAutoAddedFinalDisconnectCall);
}
public Main()
{
this.executionStarted = false;
this.executionFinished = false;
this.disconnectFlowPending = false;
this.eventBuffer = new BufferBlock<AbsEvent>();
this.currentComponentIndex = 0;
this.mainFlowComponentList = new List<AbsComponent>();
this.disconnectFlowComponentList = new List<AbsComponent>();
this.errorFlowComponentList = new List<AbsComponent>();
this.currentFlowComponentList = mainFlowComponentList;
this.timerManager = new TimerManager();
this.timerManager.OnTimeout += (state) => eventBuffer.Post(new TimeoutEvent(state));
this.variableMap = new Dictionary<string, Variable>();
AbsTextToSpeechEngine textToSpeechEngine = null;
AbsSpeechToTextEngine speechToTextEngine = null;
this.onlineServices = new OnlineServices(textToSpeechEngine, speechToTextEngine);
}
public override void Start()
{
string callID = MyCall?.Caller["chid"] ?? "Unknown";
string logHeader = $"_39_Profilstatus_mitExtension - CallID {callID}";
this.logFormatter = new LogFormatter(MyCall, logHeader, "Callflow");
this.promptQueue = new PromptQueue(this, MyCall, "_39_Profilstatus_mitExtension", logHeader);
this.tempWavFileManager = new TempWavFileManager(logFormatter);
this.timerManager.CallStarted();
this.officeHoursManager = new OfficeHoursManager(MyCall);
logFormatter.Info($"ConnectionStatus:`{MyCall.Status}`");
if (MyCall.Status == ConnectionStatus.Ringing)
MyCall.AssureMedia().ContinueWith(_ => StartInternal(logHeader, callID));
else
StartInternal(logHeader, callID);
}
private void StartInternal(string logHeader, string callID)
{
logFormatter.Trace("SetBackgroundAudio to false");
MyCall.SetBackgroundAudio(false, new string[] { });
logFormatter.Trace("Initialize components");
InitializeComponents(this, MyCall, logHeader);
logFormatter.Trace("Initialize variables");
InitializeVariables(callID);
MyCall.OnTerminated += () => eventBuffer.Post(new CallTerminatedEvent());
MyCall.OnDTMFInput += x => eventBuffer.Post(new DTMFReceivedEvent(x));
logFormatter.Trace("Start executing main flow...");
eventBuffer.Post(new StartEvent());
Task.Run(() => EventProcessingLoop());
}
public void PostStartEvent()
{
eventBuffer.Post(new StartEvent());
}
public void PostDTMFReceivedEvent(char digit)
{
eventBuffer.Post(new DTMFReceivedEvent(digit));
}
public void PostPromptPlayedEvent()
{
eventBuffer.Post(new PromptPlayedEvent());
}
public void PostTransferFailedEvent()
{
eventBuffer.Post(new TransferFailedEvent());
}
public void PostMakeCallResultEvent(bool result)
{
eventBuffer.Post(new MakeCallResultEvent(result));
}
public void PostCallTerminatedEvent()
{
eventBuffer.Post(new CallTerminatedEvent());
}
public void PostTimeoutEvent(object state)
{
eventBuffer.Post(new TimeoutEvent(state));
}
private async Task EventProcessingLoop()
{
executionStarted = true;
while (!executionFinished)
{
AbsEvent evt = await eventBuffer.ReceiveAsync();
await evt?.ProcessEvent(this);
}
if (scope != null) scope.Dispose();
}
public async Task ProcessStart()
{
try
{
EventResults eventResult;
do
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("Start executing component '" + currentComponent.Name + "'");
eventResult = await currentComponent.Start(timerManager, variableMap, tempWavFileManager, promptQueue);
}
while (CheckEventResult(eventResult) == EventResults.MoveToNextComponent);
if (eventResult == EventResults.Exit) executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessDTMFReceived(char digit)
{
try
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnDTMFReceived for component '" + currentComponent.Name + "' - Digit: '" + digit + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnDTMFReceived(timerManager, variableMap, tempWavFileManager, promptQueue, digit));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessPromptPlayed()
{
try
{
promptQueue.NotifyPlayFinished();
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnPromptPlayed for component '" + currentComponent.Name + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnPromptPlayed(timerManager, variableMap, tempWavFileManager, promptQueue));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessTransferFailed()
{
try
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnTransferFailed for component '" + currentComponent.Name + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnTransferFailed(timerManager, variableMap, tempWavFileManager, promptQueue));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessMakeCallResult(bool result)
{
try
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnMakeCallResult for component '" + currentComponent.Name + "' - Result: '" + result + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnMakeCallResult(timerManager, variableMap, tempWavFileManager, promptQueue, result));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessCallTerminated()
{
try
{
if (executionStarted)
{
// First notify the call termination to the current component
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnCallTerminated for component '" + currentComponent.Name + "'");
// Don't wrap around CheckEventResult, because the call has been already disconnected,
// and the following action to execute depends on the returned value.
EventResults eventResult = await currentComponent.OnCallTerminated(timerManager, variableMap, tempWavFileManager, promptQueue);
if (eventResult == EventResults.MoveToNextComponent)
{
// Next, if the current component has completed its job, execute the disconnect flow
await ExecuteDisconnectFlow();
}
else if (eventResult == EventResults.Wait)
{
// If the user component needs more events, wait for it to finish, and signal here that we need to execute
// the disconnect handler flow of the callflow next...
disconnectFlowPending = true;
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
finally
{
// Finally, delete temporary files
tempWavFileManager.DeleteFilesAndFolders();
}
}
public async Task ProcessTimeout(object state)
{
try
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnTimeout for component '" + currentComponent.Name + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnTimeout(timerManager, variableMap, tempWavFileManager, promptQueue, state));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
}
}

View File

@ -0,0 +1,28 @@
# Profilstatus wechseln
### 39_Profilstatus_mitExtension ###
Das Skript *39 ermöglicht das wechseln des Profilstatus einer beliebigen Nebenstelle
Mittels DTMF wird zuerst die Nebenstelle abgefragt und dann die ID des Status (0-4, wobei 0 = Verfügbar).
Bei Tischtelefonen kann dies wie unten beschrieben auf BLF Tasten gelegt werden.
Bei Apps und DECT Telefonen kann die Zielrufnummer nur über die Telefontastatur eingegeben werden.
Nutzungsbeispiel: *39 anrufen, "40#2#" eingeben um für die Nebenstelle 40 auf "Bitte nicht stören umzuschalten.
## Funktion auf BLF Tasten von Tischtelefonen legen ##
Die CFDs können ohne weitere Interaktion auf BLFs von Tischtelefonen provisioniert werden. Der Syntax ist vom Hersteller abhängig.
- Yealink BLF Tasten als indiv. Kurzwahl definieren :
z.B. Wtlg Handy: *72,0172123456#<br>
z.B. Wtlg Nst10: *72,10#<br>
z.B. Wtlg aus: *73<br>
- Fanvil BLF Tasten als indiv. Kurzwahl definieren :<br>
z.B. Wtlg Handy: *72,0172123456#<br>
z.B. Wtlg Nst10: *72,10#<br>
z.B. Wtlg aus: *73<br>
- Snom (ungetestet) BLF Tasten als indiv. Kurzwahl definieren:<br>
z.B. Wtlg Handy: *72;dtmf=0172123456#<br>
z.B. Wtlg Nst10: *72;dtmf=10#<br>
z.B. Wtlg aus: *73<br>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<Graphical_Application_Designer_Project>
<Version>2.1</Version>
<Files>
<File path="Main.flow" type="callflow" />
<File path="setStateAndFWDestination.comp" type="component" />
</Files>
<Variables>
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Variable>
<Name>ExtensionNr</Name>
<ShowScopeProperty>false</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
<Variable>
<Name>AuswahlOpt</Name>
<ShowScopeProperty>false</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
<Variable>
<Name>ZielNr</Name>
<ShowScopeProperty>false</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
</ArrayOfVariable>
</Variables>
<DebugBuildSuccessful>False</DebugBuildSuccessful>
<ReleaseBuildSuccessful>True</ReleaseBuildSuccessful>
<DebugBuildNumber>0</DebugBuildNumber>
<ReleaseBuildNumber>56</ReleaseBuildNumber>
<ChangedSinceLastDebugBuild>True</ChangedSinceLastDebugBuild>
<DoNotAskForExtension>False</DoNotAskForExtension>
<Extension>*721</Extension>
<OnlineServicesTextToSpeechEngine>None</OnlineServicesTextToSpeechEngine>
<OnlineServicesSpeechToTextEngine>None</OnlineServicesSpeechToTextEngine>
<AmazonClientID>
</AmazonClientID>
<AmazonClientSecret>
</AmazonClientSecret>
<AmazonRegion>us-east-2</AmazonRegion>
<AmazonLexicons>
</AmazonLexicons>
<GoogleCloudServiceAccountKeyFileName>
</GoogleCloudServiceAccountKeyFileName>
<GoogleCloudServiceAccountKeyJSON>
</GoogleCloudServiceAccountKeyJSON>
</Graphical_Application_Designer_Project>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<File>
<Version>2.1</Version>
<Variables>
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
</Variables>
<Flows>
<MainFlow>
<ns0:MainFlow Description="Callflow execution path." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e">
<ns0:LoggerComponent Tag="" DebugModeActive="False" Text="&quot;huhu&quot;" Level="Info" x:Name="Logger1" />
<ns0:UserInputComponent AcceptDtmfInput="True" FinalDigitTimeout="2" StopDigit="DigitPound" IsValidDigit_3="True" IsValidDigit_0="True" IsValidDigit_1="True" IsValidDigit_Pound="False" InvalidDigitPromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;FehlerBeiDerEingabe-vicky.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;" IsValidDigit_7="True" IsValidDigit_4="True" IsValidDigit_5="True" SubsequentPromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;leer50ms.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;" IsValidDigit_2="True" InterDigitTimeout="3" IsValidDigit_6="True" MinDigits="1" InitialPromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;WecheNebenstelleSollWeitergeitetwerden.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;" x:Name="InputExtension" MaxRetryCount="3" DebugModeActive="False" FirstDigitTimeout="5" MaxDigits="14" Tag="" IsValidDigit_8="True" IsValidDigit_9="True" IsValidDigit_Star="False" TimeoutPromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;">
<ns0:ComponentBranch DisplayedText="Valid Input" Description="Execution path when the specified branch is activated." Tag="" DebugModeActive="False" x:Name="componentBranch3" />
<ns0:ComponentBranch DisplayedText="Invalid Input" Description="Execution path when the specified branch is activated." Tag="" DebugModeActive="False" x:Name="componentBranch4" />
</ns0:UserInputComponent>
<ns0:UserInputComponent AcceptDtmfInput="True" FinalDigitTimeout="2" StopDigit="DigitPound" IsValidDigit_3="True" IsValidDigit_0="True" IsValidDigit_1="True" IsValidDigit_Pound="False" InvalidDigitPromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;FehlerBeiDerEingabe-vicky.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;" IsValidDigit_7="True" IsValidDigit_4="True" IsValidDigit_5="True" SubsequentPromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;leer50ms.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;" IsValidDigit_2="True" InterDigitTimeout="3" IsValidDigit_6="True" MinDigits="1" InitialPromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;Bitte geben Sie die Zielnummer der Weiterleitung ein oder.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;" x:Name="InputDestination" MaxRetryCount="3" DebugModeActive="False" FirstDigitTimeout="5" MaxDigits="14" Tag="" IsValidDigit_8="True" IsValidDigit_9="True" IsValidDigit_Star="False" TimeoutPromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;">
<ns0:ComponentBranch DisplayedText="Valid Input" Description="Execution path when the specified branch is activated." Tag="" DebugModeActive="False" x:Name="componentBranch1" />
<ns0:ComponentBranch DisplayedText="Invalid Input" Description="Execution path when the specified branch is activated." Tag="" DebugModeActive="False" x:Name="componentBranch2" />
</ns0:UserInputComponent>
<ns0:VariableAssignmentComponent VariableName="project$.ExtensionNr" Tag="" DebugModeActive="False" Expression="InputExtension.Buffer" x:Name="variableAssignmentExtensionNr" />
<ns0:VariableAssignmentComponent VariableName="project$.ZielNr" Tag="" DebugModeActive="False" Expression="InputDestination.Buffer" x:Name="variableAssignmentZielNr" />
<ns0:UserComponent PublicProperties="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfUserProperty xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;UserProperty&gt;&lt;Name&gt;strDestNo&lt;/Name&gt;&lt;Value xsi:type=&quot;xsd:string&quot;&gt;project$.ZielNr&lt;/Value&gt;&lt;/UserProperty&gt;&lt;UserProperty&gt;&lt;Name&gt;strExtensionNo&lt;/Name&gt;&lt;Value xsi:type=&quot;xsd:string&quot;&gt;project$.ExtensionNr&lt;/Value&gt;&lt;/UserProperty&gt;&lt;/ArrayOfUserProperty&gt;" RelativeFilePath="setStateAndFWDestination.comp" Tag="" DebugModeActive="False" x:Name="setStateAndFWDestination1" />
</ns0:MainFlow>
</MainFlow>
<ErrorHandlerFlow>
<ns0:ErrorHandlerFlow Description="Execution path when an error ocurrs." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
</ErrorHandlerFlow>
<DisconnectHandlerFlow>
<ns0:DisconnectHandlerFlow Description="Execution path since the call gets disconnected." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
</DisconnectHandlerFlow>
</Flows>
</File>

View File

@ -0,0 +1,773 @@
using CallFlow.CFD;
using CallFlow;
using MimeKit;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks.Dataflow;
using System.Threading.Tasks;
using System.Threading;
using System;
using TCX.Configuration;
namespace _721_dynWeiterleitung_mitExtension_an
{
public class Main : ScriptBase<Main>, ICallflow, ICallflowProcessor
{
private bool executionStarted;
private bool executionFinished;
private bool disconnectFlowPending;
private BufferBlock<AbsEvent> eventBuffer;
private int currentComponentIndex;
private List<AbsComponent> mainFlowComponentList;
private List<AbsComponent> disconnectFlowComponentList;
private List<AbsComponent> errorFlowComponentList;
private List<AbsComponent> currentFlowComponentList;
private LogFormatter logFormatter;
private TimerManager timerManager;
private Dictionary<string, Variable> variableMap;
private TempWavFileManager tempWavFileManager;
private PromptQueue promptQueue;
private OnlineServices onlineServices;
private OfficeHoursManager officeHoursManager;
private CfdAppScope scope;
private void DisconnectCallAndExitCallflow()
{
if (currentFlowComponentList == disconnectFlowComponentList)
logFormatter.Trace("Callflow finished...");
else
{
logFormatter.Trace("Callflow finished, disconnecting call...");
MyCall.Terminate();
}
}
private async Task ExecuteErrorFlow()
{
if (currentFlowComponentList == errorFlowComponentList)
{
logFormatter.Trace("Error during error handler flow, exiting callflow...");
DisconnectCallAndExitCallflow();
}
else if (currentFlowComponentList == disconnectFlowComponentList)
{
logFormatter.Trace("Error during disconnect handler flow, exiting callflow...");
executionFinished = true;
}
else
{
currentFlowComponentList = errorFlowComponentList;
currentComponentIndex = 0;
if (errorFlowComponentList.Count > 0)
{
logFormatter.Trace("Start executing error handler flow...");
await ProcessStart();
}
else
{
logFormatter.Trace("Error handler flow is empty...");
DisconnectCallAndExitCallflow();
}
}
}
private async Task ExecuteDisconnectFlow()
{
currentFlowComponentList = disconnectFlowComponentList;
currentComponentIndex = 0;
disconnectFlowPending = false;
if (disconnectFlowComponentList.Count > 0)
{
logFormatter.Trace("Start executing disconnect handler flow...");
await ProcessStart();
}
else
{
logFormatter.Trace("Disconnect handler flow is empty...");
executionFinished = true;
}
}
private EventResults CheckEventResult(EventResults eventResult)
{
if (eventResult == EventResults.MoveToNextComponent && ++currentComponentIndex == currentFlowComponentList.Count)
{
DisconnectCallAndExitCallflow();
return EventResults.Exit;
}
else if (eventResult == EventResults.Exit)
DisconnectCallAndExitCallflow();
return eventResult;
}
private void InitializeVariables(string callID)
{
// Call variables
variableMap["session.ani"] = new Variable(MyCall.Caller.CallerID);
variableMap["session.callid"] = new Variable(callID);
variableMap["session.dnis"] = new Variable(MyCall.DN.Number);
variableMap["session.did"] = new Variable(MyCall.Caller.CalledNumber);
variableMap["session.audioFolder"] = new Variable(Path.Combine(RecordingManager.Instance.AudioFolder, promptQueue.ProjectAudioFolder));
variableMap["session.transferingExtension"] = new Variable(MyCall.ReferredByDN?.Number ?? string.Empty);
variableMap["session.forwardingExtension"] = new Variable(MyCall.OnBehalfOf?.Number ?? string.Empty);
// Standard variables
variableMap["RecordResult.NothingRecorded"] = new Variable(RecordComponent.RecordResults.NothingRecorded);
variableMap["RecordResult.StopDigit"] = new Variable(RecordComponent.RecordResults.StopDigit);
variableMap["RecordResult.Completed"] = new Variable(RecordComponent.RecordResults.Completed);
variableMap["MenuResult.Timeout"] = new Variable(MenuComponent.MenuResults.Timeout);
variableMap["MenuResult.InvalidOption"] = new Variable(MenuComponent.MenuResults.InvalidOption);
variableMap["MenuResult.ValidOption"] = new Variable(MenuComponent.MenuResults.ValidOption);
variableMap["UserInputResult.Timeout"] = new Variable(UserInputComponent.UserInputResults.Timeout);
variableMap["UserInputResult.InvalidDigits"] = new Variable(UserInputComponent.UserInputResults.InvalidDigits);
variableMap["UserInputResult.ValidDigits"] = new Variable(UserInputComponent.UserInputResults.ValidDigits);
variableMap["VoiceInputResult.Timeout"] = new Variable(VoiceInputComponent.VoiceInputResults.Timeout);
variableMap["VoiceInputResult.InvalidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.InvalidInput);
variableMap["VoiceInputResult.ValidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidInput);
variableMap["VoiceInputResult.ValidDtmfInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidDtmfInput);
// User variables
variableMap["project$.ExtensionNr"] = new Variable("");
variableMap["project$.AuswahlOpt"] = new Variable("");
variableMap["project$.ZielNr"] = new Variable("");
variableMap["RecordResult.NothingRecorded"] = new Variable(RecordComponent.RecordResults.NothingRecorded);
variableMap["RecordResult.StopDigit"] = new Variable(RecordComponent.RecordResults.StopDigit);
variableMap["RecordResult.Completed"] = new Variable(RecordComponent.RecordResults.Completed);
variableMap["MenuResult.Timeout"] = new Variable(MenuComponent.MenuResults.Timeout);
variableMap["MenuResult.InvalidOption"] = new Variable(MenuComponent.MenuResults.InvalidOption);
variableMap["MenuResult.ValidOption"] = new Variable(MenuComponent.MenuResults.ValidOption);
variableMap["UserInputResult.Timeout"] = new Variable(UserInputComponent.UserInputResults.Timeout);
variableMap["UserInputResult.InvalidDigits"] = new Variable(UserInputComponent.UserInputResults.InvalidDigits);
variableMap["UserInputResult.ValidDigits"] = new Variable(UserInputComponent.UserInputResults.ValidDigits);
variableMap["VoiceInputResult.Timeout"] = new Variable(VoiceInputComponent.VoiceInputResults.Timeout);
variableMap["VoiceInputResult.InvalidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.InvalidInput);
variableMap["VoiceInputResult.ValidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidInput);
variableMap["VoiceInputResult.ValidDtmfInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidDtmfInput);
}
private void InitializeComponents(ICallflow callflow, ICall myCall, string logHeader)
{
scope = CfdModule.Instance.CreateScope(callflow, myCall, logHeader);
{
LoggerComponent Logger1 = scope.CreateComponent<LoggerComponent>("Logger1");
Logger1.Level = LoggerComponent.LogLevels.Info;
Logger1.TextHandler = () => { return Convert.ToString("huhu"); };
mainFlowComponentList.Add(Logger1);
UserInputComponent InputExtension = scope.CreateComponent<UserInputComponent>("InputExtension");
InputExtension.AllowDtmfInput = true;
InputExtension.MaxRetryCount = 2;
InputExtension.FirstDigitTimeout = 5000;
InputExtension.InterDigitTimeout = 3000;
InputExtension.FinalDigitTimeout = 2000;
InputExtension.MinDigits = 1;
InputExtension.MaxDigits = 14;
InputExtension.ValidDigitList.AddRange(new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' });
InputExtension.StopDigitList.AddRange(new char[] { '#' });
InputExtension.InitialPrompts.Add(new AudioFilePrompt(() => { return "WecheNebenstelleSollWeitergeitetwerden.wav"; }));
InputExtension.SubsequentPrompts.Add(new AudioFilePrompt(() => { return "leer50ms.wav"; }));
InputExtension.InvalidDigitPrompts.Add(new AudioFilePrompt(() => { return "FehlerBeiDerEingabe-vicky.wav"; }));
InputExtension.TimeoutPrompts.Add(new AudioFilePrompt(() => { return "DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav"; }));
mainFlowComponentList.Add(InputExtension);
ConditionalComponent InputExtension_Conditional = scope.CreateComponent<ConditionalComponent>("InputExtension_Conditional");
mainFlowComponentList.Add(InputExtension_Conditional);
InputExtension_Conditional.ConditionList.Add(() => { return InputExtension.Result == UserInputComponent.UserInputResults.ValidDigits; });
InputExtension_Conditional.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("InputExtension_Conditional_ValidInput"));
InputExtension_Conditional.ConditionList.Add(() => { return InputExtension.Result == UserInputComponent.UserInputResults.InvalidDigits || InputExtension.Result == UserInputComponent.UserInputResults.Timeout; });
InputExtension_Conditional.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("InputExtension_Conditional_InvalidInput"));
UserInputComponent InputDestination = scope.CreateComponent<UserInputComponent>("InputDestination");
InputDestination.AllowDtmfInput = true;
InputDestination.MaxRetryCount = 2;
InputDestination.FirstDigitTimeout = 5000;
InputDestination.InterDigitTimeout = 3000;
InputDestination.FinalDigitTimeout = 2000;
InputDestination.MinDigits = 1;
InputDestination.MaxDigits = 14;
InputDestination.ValidDigitList.AddRange(new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' });
InputDestination.StopDigitList.AddRange(new char[] { '#' });
InputDestination.InitialPrompts.Add(new AudioFilePrompt(() => { return "Bitte geben Sie die Zielnummer der Weiterleitung ein oder.wav"; }));
InputDestination.SubsequentPrompts.Add(new AudioFilePrompt(() => { return "leer50ms.wav"; }));
InputDestination.InvalidDigitPrompts.Add(new AudioFilePrompt(() => { return "FehlerBeiDerEingabe-vicky.wav"; }));
InputDestination.TimeoutPrompts.Add(new AudioFilePrompt(() => { return "DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav"; }));
mainFlowComponentList.Add(InputDestination);
ConditionalComponent InputDestination_Conditional = scope.CreateComponent<ConditionalComponent>("InputDestination_Conditional");
mainFlowComponentList.Add(InputDestination_Conditional);
InputDestination_Conditional.ConditionList.Add(() => { return InputDestination.Result == UserInputComponent.UserInputResults.ValidDigits; });
InputDestination_Conditional.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("InputDestination_Conditional_ValidInput"));
InputDestination_Conditional.ConditionList.Add(() => { return InputDestination.Result == UserInputComponent.UserInputResults.InvalidDigits || InputDestination.Result == UserInputComponent.UserInputResults.Timeout; });
InputDestination_Conditional.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("InputDestination_Conditional_InvalidInput"));
VariableAssignmentComponent variableAssignmentExtensionNr = scope.CreateComponent<VariableAssignmentComponent>("variableAssignmentExtensionNr");
variableAssignmentExtensionNr.VariableName = "project$.ExtensionNr";
variableAssignmentExtensionNr.VariableValueHandler = () => { return InputExtension.Buffer; };
mainFlowComponentList.Add(variableAssignmentExtensionNr);
VariableAssignmentComponent variableAssignmentZielNr = scope.CreateComponent<VariableAssignmentComponent>("variableAssignmentZielNr");
variableAssignmentZielNr.VariableName = "project$.ZielNr";
variableAssignmentZielNr.VariableValueHandler = () => { return InputDestination.Buffer; };
mainFlowComponentList.Add(variableAssignmentZielNr);
setStateAndFWDestination setStateAndFWDestination1 = new setStateAndFWDestination(onlineServices, officeHoursManager, scope, "setStateAndFWDestination1", callflow, myCall, logHeader);
setStateAndFWDestination1.strDestNoSetter = () => { return variableMap["project$.ZielNr"].Value; };
setStateAndFWDestination1.strExtensionNoSetter = () => { return variableMap["project$.ExtensionNr"].Value; };
mainFlowComponentList.Add(setStateAndFWDestination1);
}
{
}
{
}
// Add a final DisconnectCall component to the main and error handler flows, in order to complete pending prompt playbacks...
DisconnectCallComponent mainAutoAddedFinalDisconnectCall = scope.CreateComponent<DisconnectCallComponent>("mainAutoAddedFinalDisconnectCall");
DisconnectCallComponent errorHandlerAutoAddedFinalDisconnectCall = scope.CreateComponent<DisconnectCallComponent>("errorHandlerAutoAddedFinalDisconnectCall");
mainFlowComponentList.Add(mainAutoAddedFinalDisconnectCall);
errorFlowComponentList.Add(errorHandlerAutoAddedFinalDisconnectCall);
}
public Main()
{
this.executionStarted = false;
this.executionFinished = false;
this.disconnectFlowPending = false;
this.eventBuffer = new BufferBlock<AbsEvent>();
this.currentComponentIndex = 0;
this.mainFlowComponentList = new List<AbsComponent>();
this.disconnectFlowComponentList = new List<AbsComponent>();
this.errorFlowComponentList = new List<AbsComponent>();
this.currentFlowComponentList = mainFlowComponentList;
this.timerManager = new TimerManager();
this.timerManager.OnTimeout += (state) => eventBuffer.Post(new TimeoutEvent(state));
this.variableMap = new Dictionary<string, Variable>();
AbsTextToSpeechEngine textToSpeechEngine = null;
AbsSpeechToTextEngine speechToTextEngine = null;
this.onlineServices = new OnlineServices(textToSpeechEngine, speechToTextEngine);
}
public override void Start()
{
string callID = MyCall?.Caller["chid"] ?? "Unknown";
string logHeader = $"_721_dynWeiterleitung_mitExtension_an - CallID {callID}";
this.logFormatter = new LogFormatter(MyCall, logHeader, "Callflow");
this.promptQueue = new PromptQueue(this, MyCall, "_721_dynWeiterleitung_mitExtension_an", logHeader);
this.tempWavFileManager = new TempWavFileManager(logFormatter);
this.timerManager.CallStarted();
this.officeHoursManager = new OfficeHoursManager(MyCall);
logFormatter.Info($"ConnectionStatus:`{MyCall.Status}`");
if (MyCall.Status == ConnectionStatus.Ringing)
MyCall.AssureMedia().ContinueWith(_ => StartInternal(logHeader, callID));
else
StartInternal(logHeader, callID);
}
private void StartInternal(string logHeader, string callID)
{
logFormatter.Trace("SetBackgroundAudio to false");
MyCall.SetBackgroundAudio(false, new string[] { });
logFormatter.Trace("Initialize components");
InitializeComponents(this, MyCall, logHeader);
logFormatter.Trace("Initialize variables");
InitializeVariables(callID);
MyCall.OnTerminated += () => eventBuffer.Post(new CallTerminatedEvent());
MyCall.OnDTMFInput += x => eventBuffer.Post(new DTMFReceivedEvent(x));
logFormatter.Trace("Start executing main flow...");
eventBuffer.Post(new StartEvent());
Task.Run(() => EventProcessingLoop());
}
public void PostStartEvent()
{
eventBuffer.Post(new StartEvent());
}
public void PostDTMFReceivedEvent(char digit)
{
eventBuffer.Post(new DTMFReceivedEvent(digit));
}
public void PostPromptPlayedEvent()
{
eventBuffer.Post(new PromptPlayedEvent());
}
public void PostTransferFailedEvent()
{
eventBuffer.Post(new TransferFailedEvent());
}
public void PostMakeCallResultEvent(bool result)
{
eventBuffer.Post(new MakeCallResultEvent(result));
}
public void PostCallTerminatedEvent()
{
eventBuffer.Post(new CallTerminatedEvent());
}
public void PostTimeoutEvent(object state)
{
eventBuffer.Post(new TimeoutEvent(state));
}
private async Task EventProcessingLoop()
{
executionStarted = true;
while (!executionFinished)
{
AbsEvent evt = await eventBuffer.ReceiveAsync();
await evt?.ProcessEvent(this);
}
if (scope != null) scope.Dispose();
}
public async Task ProcessStart()
{
try
{
EventResults eventResult;
do
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("Start executing component '" + currentComponent.Name + "'");
eventResult = await currentComponent.Start(timerManager, variableMap, tempWavFileManager, promptQueue);
}
while (CheckEventResult(eventResult) == EventResults.MoveToNextComponent);
if (eventResult == EventResults.Exit) executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessDTMFReceived(char digit)
{
try
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnDTMFReceived for component '" + currentComponent.Name + "' - Digit: '" + digit + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnDTMFReceived(timerManager, variableMap, tempWavFileManager, promptQueue, digit));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessPromptPlayed()
{
try
{
promptQueue.NotifyPlayFinished();
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnPromptPlayed for component '" + currentComponent.Name + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnPromptPlayed(timerManager, variableMap, tempWavFileManager, promptQueue));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessTransferFailed()
{
try
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnTransferFailed for component '" + currentComponent.Name + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnTransferFailed(timerManager, variableMap, tempWavFileManager, promptQueue));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessMakeCallResult(bool result)
{
try
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnMakeCallResult for component '" + currentComponent.Name + "' - Result: '" + result + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnMakeCallResult(timerManager, variableMap, tempWavFileManager, promptQueue, result));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessCallTerminated()
{
try
{
if (executionStarted)
{
// First notify the call termination to the current component
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnCallTerminated for component '" + currentComponent.Name + "'");
// Don't wrap around CheckEventResult, because the call has been already disconnected,
// and the following action to execute depends on the returned value.
EventResults eventResult = await currentComponent.OnCallTerminated(timerManager, variableMap, tempWavFileManager, promptQueue);
if (eventResult == EventResults.MoveToNextComponent)
{
// Next, if the current component has completed its job, execute the disconnect flow
await ExecuteDisconnectFlow();
}
else if (eventResult == EventResults.Wait)
{
// If the user component needs more events, wait for it to finish, and signal here that we need to execute
// the disconnect handler flow of the callflow next...
disconnectFlowPending = true;
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
finally
{
// Finally, delete temporary files
tempWavFileManager.DeleteFilesAndFolders();
}
}
public async Task ProcessTimeout(object state)
{
try
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnTimeout for component '" + currentComponent.Name + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnTimeout(timerManager, variableMap, tempWavFileManager, promptQueue, state));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
// ------------------------------------------------------------------------------------------------------------
// User Defined component
// ------------------------------------------------------------------------------------------------------------
public class setStateAndFWDestination : AbsUserComponent
{
private OnlineServices onlineServices;
private OfficeHoursManager officeHoursManager;
private CfdAppScope scope;
private ObjectExpressionHandler _strDestNoHandler = null;
private ObjectExpressionHandler _strExtensionNoHandler = null;
protected override void InitializeVariables()
{
componentVariableMap["callflow$.strDestNo"] = new Variable("");
componentVariableMap["callflow$.strExtensionNo"] = new Variable("");
}
protected override void InitializeComponents()
{
Dictionary<string, Variable> variableMap = componentVariableMap;
{
SetExtDNDDest514741345ECCComponent SetExtDNDDest = new SetExtDNDDest514741345ECCComponent("SetExtDNDDest", callflow, myCall, logHeader);
SetExtDNDDest.Parameters.Add(new CallFlow.CFD.Parameter("strExtNr", () => { return variableMap["callflow$.strExtensionNo"].Value; }));
SetExtDNDDest.Parameters.Add(new CallFlow.CFD.Parameter("strDestNr", () => { return variableMap["callflow$.strDestNo"].Value; }));
mainFlowComponentList.Add(SetExtDNDDest);
ConditionalComponent CreateCondition1 = scope.CreateComponent<ConditionalComponent>("CreateCondition1");
mainFlowComponentList.Add(CreateCondition1);
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(SetExtDNDDest.ReturnValue,0)); });
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch1"));
TcxSetExtensionStatusComponent SetAvailable = scope.CreateComponent<TcxSetExtensionStatusComponent>("SetAvailable");
SetAvailable.ExtensionHandler = () => { return Convert.ToString(variableMap["callflow$.strExtensionNo"].Value); };
SetAvailable.ProfileNameHandler = () => { return "Available"; };
CreateCondition1.ContainerList[0].ComponentList.Add(SetAvailable);
PromptPlaybackComponent Weiterleitung_entfernt = scope.CreateComponent<PromptPlaybackComponent>("Weiterleitung_entfernt");
Weiterleitung_entfernt.AllowDtmfInput = true;
Weiterleitung_entfernt.Prompts.Add(new AudioFilePrompt(() => { return "Weiterleitung wurde entfernt.wav"; }));
CreateCondition1.ContainerList[0].ComponentList.Add(Weiterleitung_entfernt);
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(SetExtDNDDest.ReturnValue,4)); });
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch2"));
TcxSetExtensionStatusComponent SetExtCustom2 = scope.CreateComponent<TcxSetExtensionStatusComponent>("SetExtCustom2");
SetExtCustom2.ExtensionHandler = () => { return Convert.ToString(variableMap["callflow$.strExtensionNo"].Value); };
SetExtCustom2.ProfileNameHandler = () => { return "Custom 2"; };
CreateCondition1.ContainerList[1].ComponentList.Add(SetExtCustom2);
PromptPlaybackComponent PlayDigits = scope.CreateComponent<PromptPlaybackComponent>("PlayDigits");
PlayDigits.AllowDtmfInput = true;
PlayDigits.Prompts.Add(new AudioFilePrompt(() => { return "IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav"; }));
PlayDigits.Prompts.Add(new NumberPrompt(NumberPrompt.NumberFormats.OneByOne, () => { return Convert.ToString(variableMap["callflow$.strDestNo"].Value); }));
CreateCondition1.ContainerList[1].ComponentList.Add(PlayDigits);
}
{
}
{
}
}
public setStateAndFWDestination(OnlineServices onlineServices, OfficeHoursManager officeHoursManager,
CfdAppScope scope, string name, ICallflow callflow, ICall myCall, string logHeader) : base(name, callflow, myCall, logHeader)
{
this.onlineServices = onlineServices;
this.officeHoursManager = officeHoursManager;
this.scope = scope;
}
protected override void GetVariableValues()
{
if (_strDestNoHandler != null) componentVariableMap["callflow$.strDestNo"].Set(_strDestNoHandler());
if (_strExtensionNoHandler != null) componentVariableMap["callflow$.strExtensionNo"].Set(_strExtensionNoHandler());
}
public ObjectExpressionHandler strDestNoSetter { set { _strDestNoHandler = value; } }
public object strDestNo { get { return componentVariableMap["callflow$.strDestNo"].Value; } }
public ObjectExpressionHandler strExtensionNoSetter { set { _strExtensionNoHandler = value; } }
public object strExtensionNo { get { return componentVariableMap["callflow$.strExtensionNo"].Value; } }
private bool IsServerInHoliday(ICall myCall)
{
Tenant tenant = myCall.PS.GetTenant();
return tenant != null && tenant.IsHoliday(new DateTimeOffset(DateTime.Now));
}
private bool IsServerOfficeHourActive(ICall myCall)
{
Tenant tenant = myCall.PS.GetTenant();
if (tenant == null) return false;
string overrideOfficeTime = tenant.GetPropertyValue("OVERRIDEOFFICETIME");
if (!String.IsNullOrEmpty(overrideOfficeTime))
{
if (overrideOfficeTime == "1") // Forced to in office hours
return true;
else if (overrideOfficeTime == "2") // Forced to out of office hours
return false;
}
DateTime nowDt = DateTime.Now;
if (tenant.IsHoliday(new DateTimeOffset(nowDt))) return false;
Schedule officeHours = tenant.Hours;
Nullable<bool> result = officeHours.IsActiveTime(nowDt);
return result.GetValueOrDefault(false);
}
}
public class SetExtDNDDest514741345ECCComponent : ExternalCodeExecutionComponent
{
public List<CallFlow.CFD.Parameter> Parameters { get; } = new List<CallFlow.CFD.Parameter>();
public SetExtDNDDest514741345ECCComponent(string name, ICallflow callflow, ICall myCall, string projectName) : base(name, callflow, myCall, projectName) {}
protected override object ExecuteCode()
{
return SetExtDNDDest(Convert.ToString(Parameters[0].Value), Convert.ToString(Parameters[1].Value));
}
private object SetExtDNDDest(string strExtNr, string strDestNr)
{
var ext = PhoneSystem.Root.GetDNByNumber(strExtNr) as Extension;
var profile=ext.FwdProfiles.Where( x => x.Name == "Custom 2").First(); // 'Available', 'Away', 'Out of office', 'Custom 1', 'Custom 2', maybe parameter?
var NewStatus=-1;
if( profile != null ) {
// DestinationStruct need 3 parameter
// 1 DestinationType: 'None', 'VoiceMail', 'Extension', 'Queue', 'RingGroup', 'IVR', 'External', 'Fax', 'Boomerang' (external number),
// 'Deflect', 'VoiceMailOfDestination', 'Callback' (reserved), 'RoutePoint'
// 2 internal DN, maybe select by parameter - OR -
// 3 external number as string, maybe select by parameter
// DestinationType anhand der Zielnummer (strDestNr) bestimmen
bool isInternal = false;
bool isExtension = false;
bool isRG = false;
bool isIVR = false;
bool isQUEUE = false;
// check if strDestNr is a internal extension
var extension = PhoneSystem.Root.GetDNByNumber(strDestNr) as Extension;
if( extension != null ) {
isInternal = true;
isExtension = true;
}
// check if strDestNr is a ringgroup
var allRGs = PhoneSystem.Root.GetRingGroups();
foreach (var rg in allRGs)
{
if (rg.Number == strDestNr)
{
isInternal = true;
isRG = true;
break;
}
}
// check if strDestNr is a IVR/receptionist
var allIVRs = PhoneSystem.Root.GetIVRs();
foreach (var ivr in allIVRs)
{
if (ivr.Number == strDestNr)
{
isInternal = true;
isIVR = true;
break;
}
}
// check if strDestNr is a Queue
var allQUEUQs = PhoneSystem.Root.GetQueues();
foreach (var queue in allQUEUQs)
{
if (queue.Number == strDestNr)
{
isInternal = true;
isQUEUE = true;
break;
}
}
// define the DestinationStruct depending on the type of the destination target....
var dest = new DestinationStruct();
if (strExtNr.Equals(strDestNr)) {
// set state to available if destination number is the number of the extension
NewStatus = 0;
}
else {
if( isInternal) {
if( isExtension ) {
dest.To=DestinationType.Extension;
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
}
else if( isRG ) {
dest.To=DestinationType.RingGroup;
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
}
else if( isIVR ) {
dest.To=DestinationType.IVR;
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
}
else if( isQUEUE ) {
dest.To=DestinationType.Queue;
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
}
}
else {
dest.To=DestinationType.External;
dest.External=strDestNr; // needed if external destination
}
NewStatus = 4;
}
if( NewStatus >0)
{
// Depending on the type of status (present or absent), the forwarding must be entered in other target fields
if( profile.TypeOfRouting == RoutingType.Available) {
var route=profile.AvailableRoute; // maybe select by parameter?
route.NoAnswer.AllCalls = dest;
route.NoAnswer.Internal = dest;
route.Busy.AllCalls = route.NotRegistered.AllCalls = dest;
route.Busy.Internal = route.NotRegistered.Internal = dest;
}
if( profile.TypeOfRouting == RoutingType.Away) {
var route=profile.AwayRoute;
var external = profile.AwayRoute.External;
route.Internal.AllHours = dest; // maybe select by parameter?
route.Internal.OutOfOfficeHours = dest;
route.External.AllHours = dest;
route.External.OutOfOfficeHours = dest;
}
ext.Save();
}
}
return( NewStatus); }
}
}
}

View File

@ -0,0 +1,38 @@
## Dynamische Weiterleitungen ##
### 721_dynWeiterleitung_mitExtension_an ###
Das Skript *721 arbeitet wie 72_dynWeiterleitung_an, allerdings wird zuerst die zu ändernde Nebenstelle mittels DTMF abgefragt.
Anschließend wird mittels DTFM die Zielrufnummer der Weiterleitung abgefragt, diese in das Weiterleitungsziel des "Benutzerdefinierter Status 2" eingetragen
und der Status der angegebenen Nebenstelle auf diesen umgeschaltet.
Die Nebenstelle und das Ziel wird mittels DTMF übergeben. Bei Tischtelefonen kann dies wie unten beschrieben auf BLF Tasten gelegt werden.
Bei Apps und DECT Telefonen kann die Zielrufnummer nur über die Telefontastatur eingegeben werden.
Nutzungsbeispiel: *721 anrufen, "80#83#" eingeben um für die Nebenstelle 80 eine Weiterleitung auf die Nummer IVR 83 einzurichten
Dies kann z.B. verwendet werden um die Dummy Nebenstelle 80 zwischen dem Anrufbeantworter "81 TAG", dem Anrufbeantworter "82 Nacht" oder "83 Urlaub" umzuschalten.
## Funktion auf BLF Tasten von Tischtelefonen legen ##
Die CFDs können ohne weitere Interaktion auf BLFs von Tischtelefonen provisioniert werden. Der Syntax ist vom Hersteller abhängig.
- Yealink BLF Tasten als indiv. Kurzwahl definieren :
z.B. Wtlg Handy: *72,0172123456#<br>
z.B. Wtlg Nst10: *72,10#<br>
z.B. Wtlg aus: *73<br>
- Fanvil BLF Tasten als indiv. Kurzwahl definieren :<br>
z.B. Wtlg Handy: *72,0172123456#<br>
z.B. Wtlg Nst10: *72,10#<br>
z.B. Wtlg aus: *73<br>
- Snom (ungetestet) BLF Tasten als indiv. Kurzwahl definieren:<br>
z.B. Wtlg Handy: *72;dtmf=0172123456#<br>
z.B. Wtlg Nst10: *72;dtmf=10#<br>
z.B. Wtlg aus: *73<br>
*Quellen / Nützliche Tools*
- [Ausgangspunkt war der Schnippsel von fxbastler aus dem 3CX Forum](https://www.3cx.de/forum/threads/rufweiterleitung.101354/page-2#post-430429)
- [Text-To-Speech German Vicky](https://ttsmp3.com/text-to-speech/German/)
- [g711.org mp3 zu wav konvertieren](https://g711.org/)
- [3CX Call Control API Doku](https://downloads-global.3cx.com/downloads/misc/callcontrolapi/3CXCallControlAPI_v20.zip)

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<Graphical_Application_Designer_Project>
<Version>2.1</Version>
<Files>
<File path="Main.flow" type="callflow" />
<File path="setStateAndFWDestination.comp" type="component" />
</Files>
<Variables>
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Variable>
<Name>ExtensionNr</Name>
<ShowScopeProperty>false</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
<Variable>
<Name>AuswahlOpt</Name>
<ShowScopeProperty>false</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
<Variable>
<Name>ZielNr</Name>
<ShowScopeProperty>false</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
</ArrayOfVariable>
</Variables>
<DebugBuildSuccessful>False</DebugBuildSuccessful>
<ReleaseBuildSuccessful>True</ReleaseBuildSuccessful>
<DebugBuildNumber>0</DebugBuildNumber>
<ReleaseBuildNumber>49</ReleaseBuildNumber>
<ChangedSinceLastDebugBuild>True</ChangedSinceLastDebugBuild>
<DoNotAskForExtension>False</DoNotAskForExtension>
<Extension>*72</Extension>
<OnlineServicesTextToSpeechEngine>None</OnlineServicesTextToSpeechEngine>
<OnlineServicesSpeechToTextEngine>None</OnlineServicesSpeechToTextEngine>
<AmazonClientID>
</AmazonClientID>
<AmazonClientSecret>
</AmazonClientSecret>
<AmazonRegion>us-east-2</AmazonRegion>
<AmazonLexicons>
</AmazonLexicons>
<GoogleCloudServiceAccountKeyFileName>
</GoogleCloudServiceAccountKeyFileName>
<GoogleCloudServiceAccountKeyJSON>
</GoogleCloudServiceAccountKeyJSON>
</Graphical_Application_Designer_Project>

Binary file not shown.

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<File>
<Version>2.1</Version>
<Variables>
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
</Variables>
<Flows>
<MainFlow>
<ns0:MainFlow Description="Callflow execution path." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e">
<ns0:UserInputComponent AcceptDtmfInput="True" FinalDigitTimeout="2" StopDigit="DigitPound" IsValidDigit_3="True" IsValidDigit_0="True" IsValidDigit_1="True" IsValidDigit_Pound="False" InvalidDigitPromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;FehlerBeiDerEingabe-vicky.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;" IsValidDigit_7="True" IsValidDigit_4="True" IsValidDigit_5="True" SubsequentPromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;leer50ms.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;" IsValidDigit_2="True" InterDigitTimeout="3" IsValidDigit_6="True" MinDigits="1" InitialPromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;Bitte geben Sie die Zielnummer der Weiterleitung ein oder.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;" x:Name="InputDestination" MaxRetryCount="3" DebugModeActive="False" FirstDigitTimeout="5" MaxDigits="14" Tag="" IsValidDigit_8="True" IsValidDigit_9="True" IsValidDigit_Star="False" TimeoutPromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;AudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;">
<ns0:ComponentBranch DisplayedText="Valid Input" Description="Execution path when the specified branch is activated." Tag="" DebugModeActive="False" x:Name="componentBranch1" />
<ns0:ComponentBranch DisplayedText="Invalid Input" Description="Execution path when the specified branch is activated." Tag="" DebugModeActive="False" x:Name="componentBranch2" />
</ns0:UserInputComponent>
<ns0:VariableAssignmentComponent VariableName="project$.ExtensionNr" Tag="" DebugModeActive="False" Expression="session.ani" x:Name="variableAssignmentExtensionNr" />
<ns0:VariableAssignmentComponent VariableName="project$.ZielNr" Tag="" DebugModeActive="False" Expression="InputDestination.Buffer" x:Name="variableAssignmentZielNr" />
<ns0:UserComponent PublicProperties="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfUserProperty xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;UserProperty&gt;&lt;Name&gt;strDestNo&lt;/Name&gt;&lt;Value xsi:type=&quot;xsd:string&quot;&gt;project$.ZielNr&lt;/Value&gt;&lt;/UserProperty&gt;&lt;UserProperty&gt;&lt;Name&gt;strExtensionNo&lt;/Name&gt;&lt;Value xsi:type=&quot;xsd:string&quot;&gt;project$.ExtensionNr&lt;/Value&gt;&lt;/UserProperty&gt;&lt;/ArrayOfUserProperty&gt;" RelativeFilePath="setStateAndFWDestination.comp" Tag="" DebugModeActive="False" x:Name="setStateAndFWDestination1" />
</ns0:MainFlow>
</MainFlow>
<ErrorHandlerFlow>
<ns0:ErrorHandlerFlow Description="Execution path when an error ocurrs." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
</ErrorHandlerFlow>
<DisconnectHandlerFlow>
<ns0:DisconnectHandlerFlow Description="Execution path since the call gets disconnected." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
</DisconnectHandlerFlow>
</Flows>
</File>

View File

@ -0,0 +1,748 @@
using CallFlow.CFD;
using CallFlow;
using MimeKit;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks.Dataflow;
using System.Threading.Tasks;
using System.Threading;
using System;
using TCX.Configuration;
namespace _72_dynWeiterleitung_an
{
public class Main : ScriptBase<Main>, ICallflow, ICallflowProcessor
{
private bool executionStarted;
private bool executionFinished;
private bool disconnectFlowPending;
private BufferBlock<AbsEvent> eventBuffer;
private int currentComponentIndex;
private List<AbsComponent> mainFlowComponentList;
private List<AbsComponent> disconnectFlowComponentList;
private List<AbsComponent> errorFlowComponentList;
private List<AbsComponent> currentFlowComponentList;
private LogFormatter logFormatter;
private TimerManager timerManager;
private Dictionary<string, Variable> variableMap;
private TempWavFileManager tempWavFileManager;
private PromptQueue promptQueue;
private OnlineServices onlineServices;
private OfficeHoursManager officeHoursManager;
private CfdAppScope scope;
private void DisconnectCallAndExitCallflow()
{
if (currentFlowComponentList == disconnectFlowComponentList)
logFormatter.Trace("Callflow finished...");
else
{
logFormatter.Trace("Callflow finished, disconnecting call...");
MyCall.Terminate();
}
}
private async Task ExecuteErrorFlow()
{
if (currentFlowComponentList == errorFlowComponentList)
{
logFormatter.Trace("Error during error handler flow, exiting callflow...");
DisconnectCallAndExitCallflow();
}
else if (currentFlowComponentList == disconnectFlowComponentList)
{
logFormatter.Trace("Error during disconnect handler flow, exiting callflow...");
executionFinished = true;
}
else
{
currentFlowComponentList = errorFlowComponentList;
currentComponentIndex = 0;
if (errorFlowComponentList.Count > 0)
{
logFormatter.Trace("Start executing error handler flow...");
await ProcessStart();
}
else
{
logFormatter.Trace("Error handler flow is empty...");
DisconnectCallAndExitCallflow();
}
}
}
private async Task ExecuteDisconnectFlow()
{
currentFlowComponentList = disconnectFlowComponentList;
currentComponentIndex = 0;
disconnectFlowPending = false;
if (disconnectFlowComponentList.Count > 0)
{
logFormatter.Trace("Start executing disconnect handler flow...");
await ProcessStart();
}
else
{
logFormatter.Trace("Disconnect handler flow is empty...");
executionFinished = true;
}
}
private EventResults CheckEventResult(EventResults eventResult)
{
if (eventResult == EventResults.MoveToNextComponent && ++currentComponentIndex == currentFlowComponentList.Count)
{
DisconnectCallAndExitCallflow();
return EventResults.Exit;
}
else if (eventResult == EventResults.Exit)
DisconnectCallAndExitCallflow();
return eventResult;
}
private void InitializeVariables(string callID)
{
// Call variables
variableMap["session.ani"] = new Variable(MyCall.Caller.CallerID);
variableMap["session.callid"] = new Variable(callID);
variableMap["session.dnis"] = new Variable(MyCall.DN.Number);
variableMap["session.did"] = new Variable(MyCall.Caller.CalledNumber);
variableMap["session.audioFolder"] = new Variable(Path.Combine(RecordingManager.Instance.AudioFolder, promptQueue.ProjectAudioFolder));
variableMap["session.transferingExtension"] = new Variable(MyCall.ReferredByDN?.Number ?? string.Empty);
variableMap["session.forwardingExtension"] = new Variable(MyCall.OnBehalfOf?.Number ?? string.Empty);
// Standard variables
variableMap["RecordResult.NothingRecorded"] = new Variable(RecordComponent.RecordResults.NothingRecorded);
variableMap["RecordResult.StopDigit"] = new Variable(RecordComponent.RecordResults.StopDigit);
variableMap["RecordResult.Completed"] = new Variable(RecordComponent.RecordResults.Completed);
variableMap["MenuResult.Timeout"] = new Variable(MenuComponent.MenuResults.Timeout);
variableMap["MenuResult.InvalidOption"] = new Variable(MenuComponent.MenuResults.InvalidOption);
variableMap["MenuResult.ValidOption"] = new Variable(MenuComponent.MenuResults.ValidOption);
variableMap["UserInputResult.Timeout"] = new Variable(UserInputComponent.UserInputResults.Timeout);
variableMap["UserInputResult.InvalidDigits"] = new Variable(UserInputComponent.UserInputResults.InvalidDigits);
variableMap["UserInputResult.ValidDigits"] = new Variable(UserInputComponent.UserInputResults.ValidDigits);
variableMap["VoiceInputResult.Timeout"] = new Variable(VoiceInputComponent.VoiceInputResults.Timeout);
variableMap["VoiceInputResult.InvalidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.InvalidInput);
variableMap["VoiceInputResult.ValidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidInput);
variableMap["VoiceInputResult.ValidDtmfInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidDtmfInput);
// User variables
variableMap["project$.ExtensionNr"] = new Variable("");
variableMap["project$.AuswahlOpt"] = new Variable("");
variableMap["project$.ZielNr"] = new Variable("");
variableMap["RecordResult.NothingRecorded"] = new Variable(RecordComponent.RecordResults.NothingRecorded);
variableMap["RecordResult.StopDigit"] = new Variable(RecordComponent.RecordResults.StopDigit);
variableMap["RecordResult.Completed"] = new Variable(RecordComponent.RecordResults.Completed);
variableMap["MenuResult.Timeout"] = new Variable(MenuComponent.MenuResults.Timeout);
variableMap["MenuResult.InvalidOption"] = new Variable(MenuComponent.MenuResults.InvalidOption);
variableMap["MenuResult.ValidOption"] = new Variable(MenuComponent.MenuResults.ValidOption);
variableMap["UserInputResult.Timeout"] = new Variable(UserInputComponent.UserInputResults.Timeout);
variableMap["UserInputResult.InvalidDigits"] = new Variable(UserInputComponent.UserInputResults.InvalidDigits);
variableMap["UserInputResult.ValidDigits"] = new Variable(UserInputComponent.UserInputResults.ValidDigits);
variableMap["VoiceInputResult.Timeout"] = new Variable(VoiceInputComponent.VoiceInputResults.Timeout);
variableMap["VoiceInputResult.InvalidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.InvalidInput);
variableMap["VoiceInputResult.ValidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidInput);
variableMap["VoiceInputResult.ValidDtmfInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidDtmfInput);
}
private void InitializeComponents(ICallflow callflow, ICall myCall, string logHeader)
{
scope = CfdModule.Instance.CreateScope(callflow, myCall, logHeader);
{
UserInputComponent InputDestination = scope.CreateComponent<UserInputComponent>("InputDestination");
InputDestination.AllowDtmfInput = true;
InputDestination.MaxRetryCount = 2;
InputDestination.FirstDigitTimeout = 5000;
InputDestination.InterDigitTimeout = 3000;
InputDestination.FinalDigitTimeout = 2000;
InputDestination.MinDigits = 1;
InputDestination.MaxDigits = 14;
InputDestination.ValidDigitList.AddRange(new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' });
InputDestination.StopDigitList.AddRange(new char[] { '#' });
InputDestination.InitialPrompts.Add(new AudioFilePrompt(() => { return "Bitte geben Sie die Zielnummer der Weiterleitung ein oder.wav"; }));
InputDestination.SubsequentPrompts.Add(new AudioFilePrompt(() => { return "leer50ms.wav"; }));
InputDestination.InvalidDigitPrompts.Add(new AudioFilePrompt(() => { return "FehlerBeiDerEingabe-vicky.wav"; }));
InputDestination.TimeoutPrompts.Add(new AudioFilePrompt(() => { return "DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav"; }));
mainFlowComponentList.Add(InputDestination);
ConditionalComponent InputDestination_Conditional = scope.CreateComponent<ConditionalComponent>("InputDestination_Conditional");
mainFlowComponentList.Add(InputDestination_Conditional);
InputDestination_Conditional.ConditionList.Add(() => { return InputDestination.Result == UserInputComponent.UserInputResults.ValidDigits; });
InputDestination_Conditional.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("InputDestination_Conditional_ValidInput"));
InputDestination_Conditional.ConditionList.Add(() => { return InputDestination.Result == UserInputComponent.UserInputResults.InvalidDigits || InputDestination.Result == UserInputComponent.UserInputResults.Timeout; });
InputDestination_Conditional.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("InputDestination_Conditional_InvalidInput"));
VariableAssignmentComponent variableAssignmentExtensionNr = scope.CreateComponent<VariableAssignmentComponent>("variableAssignmentExtensionNr");
variableAssignmentExtensionNr.VariableName = "project$.ExtensionNr";
variableAssignmentExtensionNr.VariableValueHandler = () => { return variableMap["session.ani"].Value; };
mainFlowComponentList.Add(variableAssignmentExtensionNr);
VariableAssignmentComponent variableAssignmentZielNr = scope.CreateComponent<VariableAssignmentComponent>("variableAssignmentZielNr");
variableAssignmentZielNr.VariableName = "project$.ZielNr";
variableAssignmentZielNr.VariableValueHandler = () => { return InputDestination.Buffer; };
mainFlowComponentList.Add(variableAssignmentZielNr);
setStateAndFWDestination setStateAndFWDestination1 = new setStateAndFWDestination(onlineServices, officeHoursManager, scope, "setStateAndFWDestination1", callflow, myCall, logHeader);
setStateAndFWDestination1.strDestNoSetter = () => { return variableMap["project$.ZielNr"].Value; };
setStateAndFWDestination1.strExtensionNoSetter = () => { return variableMap["project$.ExtensionNr"].Value; };
mainFlowComponentList.Add(setStateAndFWDestination1);
}
{
}
{
}
// Add a final DisconnectCall component to the main and error handler flows, in order to complete pending prompt playbacks...
DisconnectCallComponent mainAutoAddedFinalDisconnectCall = scope.CreateComponent<DisconnectCallComponent>("mainAutoAddedFinalDisconnectCall");
DisconnectCallComponent errorHandlerAutoAddedFinalDisconnectCall = scope.CreateComponent<DisconnectCallComponent>("errorHandlerAutoAddedFinalDisconnectCall");
mainFlowComponentList.Add(mainAutoAddedFinalDisconnectCall);
errorFlowComponentList.Add(errorHandlerAutoAddedFinalDisconnectCall);
}
public Main()
{
this.executionStarted = false;
this.executionFinished = false;
this.disconnectFlowPending = false;
this.eventBuffer = new BufferBlock<AbsEvent>();
this.currentComponentIndex = 0;
this.mainFlowComponentList = new List<AbsComponent>();
this.disconnectFlowComponentList = new List<AbsComponent>();
this.errorFlowComponentList = new List<AbsComponent>();
this.currentFlowComponentList = mainFlowComponentList;
this.timerManager = new TimerManager();
this.timerManager.OnTimeout += (state) => eventBuffer.Post(new TimeoutEvent(state));
this.variableMap = new Dictionary<string, Variable>();
AbsTextToSpeechEngine textToSpeechEngine = null;
AbsSpeechToTextEngine speechToTextEngine = null;
this.onlineServices = new OnlineServices(textToSpeechEngine, speechToTextEngine);
}
public override void Start()
{
string callID = MyCall?.Caller["chid"] ?? "Unknown";
string logHeader = $"_72_dynWeiterleitung_an - CallID {callID}";
this.logFormatter = new LogFormatter(MyCall, logHeader, "Callflow");
this.promptQueue = new PromptQueue(this, MyCall, "_72_dynWeiterleitung_an", logHeader);
this.tempWavFileManager = new TempWavFileManager(logFormatter);
this.timerManager.CallStarted();
this.officeHoursManager = new OfficeHoursManager(MyCall);
logFormatter.Info($"ConnectionStatus:`{MyCall.Status}`");
if (MyCall.Status == ConnectionStatus.Ringing)
MyCall.AssureMedia().ContinueWith(_ => StartInternal(logHeader, callID));
else
StartInternal(logHeader, callID);
}
private void StartInternal(string logHeader, string callID)
{
logFormatter.Trace("SetBackgroundAudio to false");
MyCall.SetBackgroundAudio(false, new string[] { });
logFormatter.Trace("Initialize components");
InitializeComponents(this, MyCall, logHeader);
logFormatter.Trace("Initialize variables");
InitializeVariables(callID);
MyCall.OnTerminated += () => eventBuffer.Post(new CallTerminatedEvent());
MyCall.OnDTMFInput += x => eventBuffer.Post(new DTMFReceivedEvent(x));
logFormatter.Trace("Start executing main flow...");
eventBuffer.Post(new StartEvent());
Task.Run(() => EventProcessingLoop());
}
public void PostStartEvent()
{
eventBuffer.Post(new StartEvent());
}
public void PostDTMFReceivedEvent(char digit)
{
eventBuffer.Post(new DTMFReceivedEvent(digit));
}
public void PostPromptPlayedEvent()
{
eventBuffer.Post(new PromptPlayedEvent());
}
public void PostTransferFailedEvent()
{
eventBuffer.Post(new TransferFailedEvent());
}
public void PostMakeCallResultEvent(bool result)
{
eventBuffer.Post(new MakeCallResultEvent(result));
}
public void PostCallTerminatedEvent()
{
eventBuffer.Post(new CallTerminatedEvent());
}
public void PostTimeoutEvent(object state)
{
eventBuffer.Post(new TimeoutEvent(state));
}
private async Task EventProcessingLoop()
{
executionStarted = true;
while (!executionFinished)
{
AbsEvent evt = await eventBuffer.ReceiveAsync();
await evt?.ProcessEvent(this);
}
if (scope != null) scope.Dispose();
}
public async Task ProcessStart()
{
try
{
EventResults eventResult;
do
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("Start executing component '" + currentComponent.Name + "'");
eventResult = await currentComponent.Start(timerManager, variableMap, tempWavFileManager, promptQueue);
}
while (CheckEventResult(eventResult) == EventResults.MoveToNextComponent);
if (eventResult == EventResults.Exit) executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessDTMFReceived(char digit)
{
try
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnDTMFReceived for component '" + currentComponent.Name + "' - Digit: '" + digit + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnDTMFReceived(timerManager, variableMap, tempWavFileManager, promptQueue, digit));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessPromptPlayed()
{
try
{
promptQueue.NotifyPlayFinished();
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnPromptPlayed for component '" + currentComponent.Name + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnPromptPlayed(timerManager, variableMap, tempWavFileManager, promptQueue));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessTransferFailed()
{
try
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnTransferFailed for component '" + currentComponent.Name + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnTransferFailed(timerManager, variableMap, tempWavFileManager, promptQueue));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessMakeCallResult(bool result)
{
try
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnMakeCallResult for component '" + currentComponent.Name + "' - Result: '" + result + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnMakeCallResult(timerManager, variableMap, tempWavFileManager, promptQueue, result));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessCallTerminated()
{
try
{
if (executionStarted)
{
// First notify the call termination to the current component
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnCallTerminated for component '" + currentComponent.Name + "'");
// Don't wrap around CheckEventResult, because the call has been already disconnected,
// and the following action to execute depends on the returned value.
EventResults eventResult = await currentComponent.OnCallTerminated(timerManager, variableMap, tempWavFileManager, promptQueue);
if (eventResult == EventResults.MoveToNextComponent)
{
// Next, if the current component has completed its job, execute the disconnect flow
await ExecuteDisconnectFlow();
}
else if (eventResult == EventResults.Wait)
{
// If the user component needs more events, wait for it to finish, and signal here that we need to execute
// the disconnect handler flow of the callflow next...
disconnectFlowPending = true;
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
finally
{
// Finally, delete temporary files
tempWavFileManager.DeleteFilesAndFolders();
}
}
public async Task ProcessTimeout(object state)
{
try
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnTimeout for component '" + currentComponent.Name + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnTimeout(timerManager, variableMap, tempWavFileManager, promptQueue, state));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
// ------------------------------------------------------------------------------------------------------------
// User Defined component
// ------------------------------------------------------------------------------------------------------------
public class setStateAndFWDestination : AbsUserComponent
{
private OnlineServices onlineServices;
private OfficeHoursManager officeHoursManager;
private CfdAppScope scope;
private ObjectExpressionHandler _strDestNoHandler = null;
private ObjectExpressionHandler _strExtensionNoHandler = null;
protected override void InitializeVariables()
{
componentVariableMap["callflow$.strDestNo"] = new Variable("");
componentVariableMap["callflow$.strExtensionNo"] = new Variable("");
}
protected override void InitializeComponents()
{
Dictionary<string, Variable> variableMap = componentVariableMap;
{
SetExtDNDDest1287229307ECCComponent SetExtDNDDest = new SetExtDNDDest1287229307ECCComponent("SetExtDNDDest", callflow, myCall, logHeader);
SetExtDNDDest.Parameters.Add(new CallFlow.CFD.Parameter("strExtNr", () => { return variableMap["callflow$.strExtensionNo"].Value; }));
SetExtDNDDest.Parameters.Add(new CallFlow.CFD.Parameter("strDestNr", () => { return variableMap["callflow$.strDestNo"].Value; }));
mainFlowComponentList.Add(SetExtDNDDest);
ConditionalComponent CreateCondition1 = scope.CreateComponent<ConditionalComponent>("CreateCondition1");
mainFlowComponentList.Add(CreateCondition1);
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(SetExtDNDDest.ReturnValue,0)); });
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch1"));
TcxSetExtensionStatusComponent SetAvailable = scope.CreateComponent<TcxSetExtensionStatusComponent>("SetAvailable");
SetAvailable.ExtensionHandler = () => { return Convert.ToString(variableMap["callflow$.strExtensionNo"].Value); };
SetAvailable.ProfileNameHandler = () => { return "Available"; };
CreateCondition1.ContainerList[0].ComponentList.Add(SetAvailable);
PromptPlaybackComponent Weiterleitung_entfernt = scope.CreateComponent<PromptPlaybackComponent>("Weiterleitung_entfernt");
Weiterleitung_entfernt.AllowDtmfInput = true;
Weiterleitung_entfernt.Prompts.Add(new AudioFilePrompt(() => { return "Weiterleitung wurde entfernt.wav"; }));
CreateCondition1.ContainerList[0].ComponentList.Add(Weiterleitung_entfernt);
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(SetExtDNDDest.ReturnValue,4)); });
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch2"));
TcxSetExtensionStatusComponent SetExtCustom2 = scope.CreateComponent<TcxSetExtensionStatusComponent>("SetExtCustom2");
SetExtCustom2.ExtensionHandler = () => { return Convert.ToString(variableMap["callflow$.strExtensionNo"].Value); };
SetExtCustom2.ProfileNameHandler = () => { return "Custom 2"; };
CreateCondition1.ContainerList[1].ComponentList.Add(SetExtCustom2);
PromptPlaybackComponent PlayDigits = scope.CreateComponent<PromptPlaybackComponent>("PlayDigits");
PlayDigits.AllowDtmfInput = true;
PlayDigits.Prompts.Add(new AudioFilePrompt(() => { return "IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav"; }));
PlayDigits.Prompts.Add(new NumberPrompt(NumberPrompt.NumberFormats.OneByOne, () => { return Convert.ToString(variableMap["callflow$.strDestNo"].Value); }));
CreateCondition1.ContainerList[1].ComponentList.Add(PlayDigits);
}
{
}
{
}
}
public setStateAndFWDestination(OnlineServices onlineServices, OfficeHoursManager officeHoursManager,
CfdAppScope scope, string name, ICallflow callflow, ICall myCall, string logHeader) : base(name, callflow, myCall, logHeader)
{
this.onlineServices = onlineServices;
this.officeHoursManager = officeHoursManager;
this.scope = scope;
}
protected override void GetVariableValues()
{
if (_strDestNoHandler != null) componentVariableMap["callflow$.strDestNo"].Set(_strDestNoHandler());
if (_strExtensionNoHandler != null) componentVariableMap["callflow$.strExtensionNo"].Set(_strExtensionNoHandler());
}
public ObjectExpressionHandler strDestNoSetter { set { _strDestNoHandler = value; } }
public object strDestNo { get { return componentVariableMap["callflow$.strDestNo"].Value; } }
public ObjectExpressionHandler strExtensionNoSetter { set { _strExtensionNoHandler = value; } }
public object strExtensionNo { get { return componentVariableMap["callflow$.strExtensionNo"].Value; } }
private bool IsServerInHoliday(ICall myCall)
{
Tenant tenant = myCall.PS.GetTenant();
return tenant != null && tenant.IsHoliday(new DateTimeOffset(DateTime.Now));
}
private bool IsServerOfficeHourActive(ICall myCall)
{
Tenant tenant = myCall.PS.GetTenant();
if (tenant == null) return false;
string overrideOfficeTime = tenant.GetPropertyValue("OVERRIDEOFFICETIME");
if (!String.IsNullOrEmpty(overrideOfficeTime))
{
if (overrideOfficeTime == "1") // Forced to in office hours
return true;
else if (overrideOfficeTime == "2") // Forced to out of office hours
return false;
}
DateTime nowDt = DateTime.Now;
if (tenant.IsHoliday(new DateTimeOffset(nowDt))) return false;
Schedule officeHours = tenant.Hours;
Nullable<bool> result = officeHours.IsActiveTime(nowDt);
return result.GetValueOrDefault(false);
}
}
public class SetExtDNDDest1287229307ECCComponent : ExternalCodeExecutionComponent
{
public List<CallFlow.CFD.Parameter> Parameters { get; } = new List<CallFlow.CFD.Parameter>();
public SetExtDNDDest1287229307ECCComponent(string name, ICallflow callflow, ICall myCall, string projectName) : base(name, callflow, myCall, projectName) {}
protected override object ExecuteCode()
{
return SetExtDNDDest(Convert.ToString(Parameters[0].Value), Convert.ToString(Parameters[1].Value));
}
private object SetExtDNDDest(string strExtNr, string strDestNr)
{
var ext = PhoneSystem.Root.GetDNByNumber(strExtNr) as Extension;
var profile=ext.FwdProfiles.Where( x => x.Name == "Custom 2").First(); // 'Available', 'Away', 'Out of office', 'Custom 1', 'Custom 2', maybe parameter?
var NewStatus=-1;
if( profile != null ) {
// DestinationStruct need 3 parameter
// 1 DestinationType: 'None', 'VoiceMail', 'Extension', 'Queue', 'RingGroup', 'IVR', 'External', 'Fax', 'Boomerang' (external number),
// 'Deflect', 'VoiceMailOfDestination', 'Callback' (reserved), 'RoutePoint'
// 2 internal DN, maybe select by parameter - OR -
// 3 external number as string, maybe select by parameter
// DestinationType anhand der Zielnummer (strDestNr) bestimmen
bool isInternal = false;
bool isExtension = false;
bool isRG = false;
bool isIVR = false;
bool isQUEUE = false;
// check if strDestNr is a internal extension
var extension = PhoneSystem.Root.GetDNByNumber(strDestNr) as Extension;
if( extension != null ) {
isInternal = true;
isExtension = true;
}
// check if strDestNr is a ringgroup
var allRGs = PhoneSystem.Root.GetRingGroups();
foreach (var rg in allRGs)
{
if (rg.Number == strDestNr)
{
isInternal = true;
isRG = true;
break;
}
}
// check if strDestNr is a IVR/receptionist
var allIVRs = PhoneSystem.Root.GetIVRs();
foreach (var ivr in allIVRs)
{
if (ivr.Number == strDestNr)
{
isInternal = true;
isIVR = true;
break;
}
}
// check if strDestNr is a Queue
var allQUEUQs = PhoneSystem.Root.GetQueues();
foreach (var queue in allQUEUQs)
{
if (queue.Number == strDestNr)
{
isInternal = true;
isQUEUE = true;
break;
}
}
// define the DestinationStruct depending on the type of the destination target....
var dest = new DestinationStruct();
if (strExtNr.Equals(strDestNr)) {
// set state to available if destination number is the number of the extension
NewStatus = 0;
}
else {
if( isInternal) {
if( isExtension ) {
dest.To=DestinationType.Extension;
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
}
else if( isRG ) {
dest.To=DestinationType.RingGroup;
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
}
else if( isIVR ) {
dest.To=DestinationType.IVR;
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
}
else if( isQUEUE ) {
dest.To=DestinationType.Queue;
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
}
}
else {
dest.To=DestinationType.External;
dest.External=strDestNr; // needed if external destination
}
NewStatus = 4;
}
if( NewStatus >0)
{
// Depending on the type of status (present or absent), the forwarding must be entered in other target fields
if( profile.TypeOfRouting == RoutingType.Available) {
var route=profile.AvailableRoute; // maybe select by parameter?
route.NoAnswer.AllCalls = dest;
route.NoAnswer.Internal = dest;
route.Busy.AllCalls = route.NotRegistered.AllCalls = dest;
route.Busy.Internal = route.NotRegistered.Internal = dest;
}
if( profile.TypeOfRouting == RoutingType.Away) {
var route=profile.AwayRoute;
var external = profile.AwayRoute.External;
route.Internal.AllHours = dest; // maybe select by parameter?
route.Internal.OutOfOfficeHours = dest;
route.External.AllHours = dest;
route.External.OutOfOfficeHours = dest;
}
ext.Save();
}
}
return( NewStatus); }
}
}
}

View File

@ -0,0 +1,32 @@
## Dynamische Weiterleitungen ##
### 72_dynWeiterleitung_an ###
Das Skript *72 fragt mittels DTFM die Zielrufnummer der Weiterleitung ab, trägt diese in das Weiterleitungsziel des "Benutzerdefinierter Status 2" und schaltet den Status der anrufenden Nebenstelle auf diesen um.
Das Ziel wird mittels DTMF übergeben. Bei Tischtelefonen kann dies wie unten beschrieben auf BLF Tasten gelegt werden.
Bei Apps und DECT Telefonen kann die Zielrufnummer nur über die Telefontastatur eingegeben werden.
Nutzungsbeispiel: *72 anrufen, "0172123456#" eingeben um eine Weiterleitung auf die Nummer 0172123456 einzurichten
## Funktion auf BLF Tasten von Tischtelefonen legen ##
Die CFDs können ohne weitere Interaktion auf BLFs von Tischtelefonen provisioniert werden. Der Syntax ist vom Hersteller abhängig.
- Yealink BLF Tasten als indiv. Kurzwahl definieren :
z.B. Wtlg Handy: *72,0172123456#<br>
z.B. Wtlg Nst10: *72,10#<br>
z.B. Wtlg aus: *73<br>
- Fanvil BLF Tasten als indiv. Kurzwahl definieren :<br>
z.B. Wtlg Handy: *72,0172123456#<br>
z.B. Wtlg Nst10: *72,10#<br>
z.B. Wtlg aus: *73<br>
- Snom (ungetestet) BLF Tasten als indiv. Kurzwahl definieren:<br>
z.B. Wtlg Handy: *72;dtmf=0172123456#<br>
z.B. Wtlg Nst10: *72;dtmf=10#<br>
z.B. Wtlg aus: *73<br>
*Quellen / Nützliche Tools*
- [Ausgangspunkt war der Schnippsel von fxbastler aus dem 3CX Forum](https://www.3cx.de/forum/threads/rufweiterleitung.101354/page-2#post-430429)
- [Text-To-Speech German Vicky](https://ttsmp3.com/text-to-speech/German/)
- [g711.org mp3 zu wav konvertieren](https://g711.org/)
- [3CX Call Control API Doku](https://downloads-global.3cx.com/downloads/misc/callcontrolapi/3CXCallControlAPI_v20.zip)

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<Graphical_Application_Designer_Project>
<Version>2.1</Version>
<Files>
<File path="Main.flow" type="callflow" />
<File path="setStateAndFWDestination.comp" type="component" />
</Files>
<Variables>
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Variable>
<Name>ExtensionNr</Name>
<ShowScopeProperty>false</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
<Variable>
<Name>AuswahlOpt</Name>
<ShowScopeProperty>false</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
<Variable>
<Name>ZielNr</Name>
<ShowScopeProperty>false</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
</ArrayOfVariable>
</Variables>
<DebugBuildSuccessful>False</DebugBuildSuccessful>
<ReleaseBuildSuccessful>True</ReleaseBuildSuccessful>
<DebugBuildNumber>0</DebugBuildNumber>
<ReleaseBuildNumber>48</ReleaseBuildNumber>
<ChangedSinceLastDebugBuild>True</ChangedSinceLastDebugBuild>
<DoNotAskForExtension>False</DoNotAskForExtension>
<Extension>*73</Extension>
<OnlineServicesTextToSpeechEngine>None</OnlineServicesTextToSpeechEngine>
<OnlineServicesSpeechToTextEngine>None</OnlineServicesSpeechToTextEngine>
<AmazonClientID>
</AmazonClientID>
<AmazonClientSecret>
</AmazonClientSecret>
<AmazonRegion>us-east-2</AmazonRegion>
<AmazonLexicons>
</AmazonLexicons>
<GoogleCloudServiceAccountKeyFileName>
</GoogleCloudServiceAccountKeyFileName>
<GoogleCloudServiceAccountKeyJSON>
</GoogleCloudServiceAccountKeyJSON>
</Graphical_Application_Designer_Project>

Binary file not shown.

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<File>
<Version>2.1</Version>
<Variables>
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
</Variables>
<Flows>
<MainFlow>
<ns0:MainFlow Description="Callflow execution path." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e">
<ns0:VariableAssignmentComponent VariableName="project$.ExtensionNr" Tag="" DebugModeActive="False" Expression="session.ani" x:Name="variableAssignmentExtensionNr" />
<ns0:VariableAssignmentComponent VariableName="project$.ZielNr" Tag="" DebugModeActive="False" Expression="session.ani" x:Name="variableAssignmentZielNr" />
<ns0:UserComponent PublicProperties="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfUserProperty xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;UserProperty&gt;&lt;Name&gt;strDestNo&lt;/Name&gt;&lt;Value xsi:type=&quot;xsd:string&quot;&gt;project$.ZielNr&lt;/Value&gt;&lt;/UserProperty&gt;&lt;UserProperty&gt;&lt;Name&gt;strExtensionNo&lt;/Name&gt;&lt;Value xsi:type=&quot;xsd:string&quot;&gt;project$.ExtensionNr&lt;/Value&gt;&lt;/UserProperty&gt;&lt;/ArrayOfUserProperty&gt;" RelativeFilePath="setStateAndFWDestination.comp" Tag="" DebugModeActive="False" x:Name="setStateAndFWDestination1" />
</ns0:MainFlow>
</MainFlow>
<ErrorHandlerFlow>
<ns0:ErrorHandlerFlow Description="Execution path when an error ocurrs." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
</ErrorHandlerFlow>
<DisconnectHandlerFlow>
<ns0:DisconnectHandlerFlow Description="Execution path since the call gets disconnected." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
</DisconnectHandlerFlow>
</Flows>
</File>

View File

@ -0,0 +1,727 @@
using CallFlow.CFD;
using CallFlow;
using MimeKit;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks.Dataflow;
using System.Threading.Tasks;
using System.Threading;
using System;
using TCX.Configuration;
namespace _73_dynWeiterleitung_aus
{
public class Main : ScriptBase<Main>, ICallflow, ICallflowProcessor
{
private bool executionStarted;
private bool executionFinished;
private bool disconnectFlowPending;
private BufferBlock<AbsEvent> eventBuffer;
private int currentComponentIndex;
private List<AbsComponent> mainFlowComponentList;
private List<AbsComponent> disconnectFlowComponentList;
private List<AbsComponent> errorFlowComponentList;
private List<AbsComponent> currentFlowComponentList;
private LogFormatter logFormatter;
private TimerManager timerManager;
private Dictionary<string, Variable> variableMap;
private TempWavFileManager tempWavFileManager;
private PromptQueue promptQueue;
private OnlineServices onlineServices;
private OfficeHoursManager officeHoursManager;
private CfdAppScope scope;
private void DisconnectCallAndExitCallflow()
{
if (currentFlowComponentList == disconnectFlowComponentList)
logFormatter.Trace("Callflow finished...");
else
{
logFormatter.Trace("Callflow finished, disconnecting call...");
MyCall.Terminate();
}
}
private async Task ExecuteErrorFlow()
{
if (currentFlowComponentList == errorFlowComponentList)
{
logFormatter.Trace("Error during error handler flow, exiting callflow...");
DisconnectCallAndExitCallflow();
}
else if (currentFlowComponentList == disconnectFlowComponentList)
{
logFormatter.Trace("Error during disconnect handler flow, exiting callflow...");
executionFinished = true;
}
else
{
currentFlowComponentList = errorFlowComponentList;
currentComponentIndex = 0;
if (errorFlowComponentList.Count > 0)
{
logFormatter.Trace("Start executing error handler flow...");
await ProcessStart();
}
else
{
logFormatter.Trace("Error handler flow is empty...");
DisconnectCallAndExitCallflow();
}
}
}
private async Task ExecuteDisconnectFlow()
{
currentFlowComponentList = disconnectFlowComponentList;
currentComponentIndex = 0;
disconnectFlowPending = false;
if (disconnectFlowComponentList.Count > 0)
{
logFormatter.Trace("Start executing disconnect handler flow...");
await ProcessStart();
}
else
{
logFormatter.Trace("Disconnect handler flow is empty...");
executionFinished = true;
}
}
private EventResults CheckEventResult(EventResults eventResult)
{
if (eventResult == EventResults.MoveToNextComponent && ++currentComponentIndex == currentFlowComponentList.Count)
{
DisconnectCallAndExitCallflow();
return EventResults.Exit;
}
else if (eventResult == EventResults.Exit)
DisconnectCallAndExitCallflow();
return eventResult;
}
private void InitializeVariables(string callID)
{
// Call variables
variableMap["session.ani"] = new Variable(MyCall.Caller.CallerID);
variableMap["session.callid"] = new Variable(callID);
variableMap["session.dnis"] = new Variable(MyCall.DN.Number);
variableMap["session.did"] = new Variable(MyCall.Caller.CalledNumber);
variableMap["session.audioFolder"] = new Variable(Path.Combine(RecordingManager.Instance.AudioFolder, promptQueue.ProjectAudioFolder));
variableMap["session.transferingExtension"] = new Variable(MyCall.ReferredByDN?.Number ?? string.Empty);
variableMap["session.forwardingExtension"] = new Variable(MyCall.OnBehalfOf?.Number ?? string.Empty);
// Standard variables
variableMap["RecordResult.NothingRecorded"] = new Variable(RecordComponent.RecordResults.NothingRecorded);
variableMap["RecordResult.StopDigit"] = new Variable(RecordComponent.RecordResults.StopDigit);
variableMap["RecordResult.Completed"] = new Variable(RecordComponent.RecordResults.Completed);
variableMap["MenuResult.Timeout"] = new Variable(MenuComponent.MenuResults.Timeout);
variableMap["MenuResult.InvalidOption"] = new Variable(MenuComponent.MenuResults.InvalidOption);
variableMap["MenuResult.ValidOption"] = new Variable(MenuComponent.MenuResults.ValidOption);
variableMap["UserInputResult.Timeout"] = new Variable(UserInputComponent.UserInputResults.Timeout);
variableMap["UserInputResult.InvalidDigits"] = new Variable(UserInputComponent.UserInputResults.InvalidDigits);
variableMap["UserInputResult.ValidDigits"] = new Variable(UserInputComponent.UserInputResults.ValidDigits);
variableMap["VoiceInputResult.Timeout"] = new Variable(VoiceInputComponent.VoiceInputResults.Timeout);
variableMap["VoiceInputResult.InvalidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.InvalidInput);
variableMap["VoiceInputResult.ValidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidInput);
variableMap["VoiceInputResult.ValidDtmfInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidDtmfInput);
// User variables
variableMap["project$.ExtensionNr"] = new Variable("");
variableMap["project$.AuswahlOpt"] = new Variable("");
variableMap["project$.ZielNr"] = new Variable("");
variableMap["RecordResult.NothingRecorded"] = new Variable(RecordComponent.RecordResults.NothingRecorded);
variableMap["RecordResult.StopDigit"] = new Variable(RecordComponent.RecordResults.StopDigit);
variableMap["RecordResult.Completed"] = new Variable(RecordComponent.RecordResults.Completed);
variableMap["MenuResult.Timeout"] = new Variable(MenuComponent.MenuResults.Timeout);
variableMap["MenuResult.InvalidOption"] = new Variable(MenuComponent.MenuResults.InvalidOption);
variableMap["MenuResult.ValidOption"] = new Variable(MenuComponent.MenuResults.ValidOption);
variableMap["UserInputResult.Timeout"] = new Variable(UserInputComponent.UserInputResults.Timeout);
variableMap["UserInputResult.InvalidDigits"] = new Variable(UserInputComponent.UserInputResults.InvalidDigits);
variableMap["UserInputResult.ValidDigits"] = new Variable(UserInputComponent.UserInputResults.ValidDigits);
variableMap["VoiceInputResult.Timeout"] = new Variable(VoiceInputComponent.VoiceInputResults.Timeout);
variableMap["VoiceInputResult.InvalidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.InvalidInput);
variableMap["VoiceInputResult.ValidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidInput);
variableMap["VoiceInputResult.ValidDtmfInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidDtmfInput);
}
private void InitializeComponents(ICallflow callflow, ICall myCall, string logHeader)
{
scope = CfdModule.Instance.CreateScope(callflow, myCall, logHeader);
{
VariableAssignmentComponent variableAssignmentExtensionNr = scope.CreateComponent<VariableAssignmentComponent>("variableAssignmentExtensionNr");
variableAssignmentExtensionNr.VariableName = "project$.ExtensionNr";
variableAssignmentExtensionNr.VariableValueHandler = () => { return variableMap["session.ani"].Value; };
mainFlowComponentList.Add(variableAssignmentExtensionNr);
VariableAssignmentComponent variableAssignmentZielNr = scope.CreateComponent<VariableAssignmentComponent>("variableAssignmentZielNr");
variableAssignmentZielNr.VariableName = "project$.ZielNr";
variableAssignmentZielNr.VariableValueHandler = () => { return variableMap["session.ani"].Value; };
mainFlowComponentList.Add(variableAssignmentZielNr);
setStateAndFWDestination setStateAndFWDestination1 = new setStateAndFWDestination(onlineServices, officeHoursManager, scope, "setStateAndFWDestination1", callflow, myCall, logHeader);
setStateAndFWDestination1.strDestNoSetter = () => { return variableMap["project$.ZielNr"].Value; };
setStateAndFWDestination1.strExtensionNoSetter = () => { return variableMap["project$.ExtensionNr"].Value; };
mainFlowComponentList.Add(setStateAndFWDestination1);
}
{
}
{
}
// Add a final DisconnectCall component to the main and error handler flows, in order to complete pending prompt playbacks...
DisconnectCallComponent mainAutoAddedFinalDisconnectCall = scope.CreateComponent<DisconnectCallComponent>("mainAutoAddedFinalDisconnectCall");
DisconnectCallComponent errorHandlerAutoAddedFinalDisconnectCall = scope.CreateComponent<DisconnectCallComponent>("errorHandlerAutoAddedFinalDisconnectCall");
mainFlowComponentList.Add(mainAutoAddedFinalDisconnectCall);
errorFlowComponentList.Add(errorHandlerAutoAddedFinalDisconnectCall);
}
public Main()
{
this.executionStarted = false;
this.executionFinished = false;
this.disconnectFlowPending = false;
this.eventBuffer = new BufferBlock<AbsEvent>();
this.currentComponentIndex = 0;
this.mainFlowComponentList = new List<AbsComponent>();
this.disconnectFlowComponentList = new List<AbsComponent>();
this.errorFlowComponentList = new List<AbsComponent>();
this.currentFlowComponentList = mainFlowComponentList;
this.timerManager = new TimerManager();
this.timerManager.OnTimeout += (state) => eventBuffer.Post(new TimeoutEvent(state));
this.variableMap = new Dictionary<string, Variable>();
AbsTextToSpeechEngine textToSpeechEngine = null;
AbsSpeechToTextEngine speechToTextEngine = null;
this.onlineServices = new OnlineServices(textToSpeechEngine, speechToTextEngine);
}
public override void Start()
{
string callID = MyCall?.Caller["chid"] ?? "Unknown";
string logHeader = $"_73_dynWeiterleitung_aus - CallID {callID}";
this.logFormatter = new LogFormatter(MyCall, logHeader, "Callflow");
this.promptQueue = new PromptQueue(this, MyCall, "_73_dynWeiterleitung_aus", logHeader);
this.tempWavFileManager = new TempWavFileManager(logFormatter);
this.timerManager.CallStarted();
this.officeHoursManager = new OfficeHoursManager(MyCall);
logFormatter.Info($"ConnectionStatus:`{MyCall.Status}`");
if (MyCall.Status == ConnectionStatus.Ringing)
MyCall.AssureMedia().ContinueWith(_ => StartInternal(logHeader, callID));
else
StartInternal(logHeader, callID);
}
private void StartInternal(string logHeader, string callID)
{
logFormatter.Trace("SetBackgroundAudio to false");
MyCall.SetBackgroundAudio(false, new string[] { });
logFormatter.Trace("Initialize components");
InitializeComponents(this, MyCall, logHeader);
logFormatter.Trace("Initialize variables");
InitializeVariables(callID);
MyCall.OnTerminated += () => eventBuffer.Post(new CallTerminatedEvent());
MyCall.OnDTMFInput += x => eventBuffer.Post(new DTMFReceivedEvent(x));
logFormatter.Trace("Start executing main flow...");
eventBuffer.Post(new StartEvent());
Task.Run(() => EventProcessingLoop());
}
public void PostStartEvent()
{
eventBuffer.Post(new StartEvent());
}
public void PostDTMFReceivedEvent(char digit)
{
eventBuffer.Post(new DTMFReceivedEvent(digit));
}
public void PostPromptPlayedEvent()
{
eventBuffer.Post(new PromptPlayedEvent());
}
public void PostTransferFailedEvent()
{
eventBuffer.Post(new TransferFailedEvent());
}
public void PostMakeCallResultEvent(bool result)
{
eventBuffer.Post(new MakeCallResultEvent(result));
}
public void PostCallTerminatedEvent()
{
eventBuffer.Post(new CallTerminatedEvent());
}
public void PostTimeoutEvent(object state)
{
eventBuffer.Post(new TimeoutEvent(state));
}
private async Task EventProcessingLoop()
{
executionStarted = true;
while (!executionFinished)
{
AbsEvent evt = await eventBuffer.ReceiveAsync();
await evt?.ProcessEvent(this);
}
if (scope != null) scope.Dispose();
}
public async Task ProcessStart()
{
try
{
EventResults eventResult;
do
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("Start executing component '" + currentComponent.Name + "'");
eventResult = await currentComponent.Start(timerManager, variableMap, tempWavFileManager, promptQueue);
}
while (CheckEventResult(eventResult) == EventResults.MoveToNextComponent);
if (eventResult == EventResults.Exit) executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessDTMFReceived(char digit)
{
try
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnDTMFReceived for component '" + currentComponent.Name + "' - Digit: '" + digit + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnDTMFReceived(timerManager, variableMap, tempWavFileManager, promptQueue, digit));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessPromptPlayed()
{
try
{
promptQueue.NotifyPlayFinished();
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnPromptPlayed for component '" + currentComponent.Name + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnPromptPlayed(timerManager, variableMap, tempWavFileManager, promptQueue));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessTransferFailed()
{
try
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnTransferFailed for component '" + currentComponent.Name + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnTransferFailed(timerManager, variableMap, tempWavFileManager, promptQueue));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessMakeCallResult(bool result)
{
try
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnMakeCallResult for component '" + currentComponent.Name + "' - Result: '" + result + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnMakeCallResult(timerManager, variableMap, tempWavFileManager, promptQueue, result));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessCallTerminated()
{
try
{
if (executionStarted)
{
// First notify the call termination to the current component
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnCallTerminated for component '" + currentComponent.Name + "'");
// Don't wrap around CheckEventResult, because the call has been already disconnected,
// and the following action to execute depends on the returned value.
EventResults eventResult = await currentComponent.OnCallTerminated(timerManager, variableMap, tempWavFileManager, promptQueue);
if (eventResult == EventResults.MoveToNextComponent)
{
// Next, if the current component has completed its job, execute the disconnect flow
await ExecuteDisconnectFlow();
}
else if (eventResult == EventResults.Wait)
{
// If the user component needs more events, wait for it to finish, and signal here that we need to execute
// the disconnect handler flow of the callflow next...
disconnectFlowPending = true;
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
finally
{
// Finally, delete temporary files
tempWavFileManager.DeleteFilesAndFolders();
}
}
public async Task ProcessTimeout(object state)
{
try
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnTimeout for component '" + currentComponent.Name + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnTimeout(timerManager, variableMap, tempWavFileManager, promptQueue, state));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
// ------------------------------------------------------------------------------------------------------------
// User Defined component
// ------------------------------------------------------------------------------------------------------------
public class setStateAndFWDestination : AbsUserComponent
{
private OnlineServices onlineServices;
private OfficeHoursManager officeHoursManager;
private CfdAppScope scope;
private ObjectExpressionHandler _strDestNoHandler = null;
private ObjectExpressionHandler _strExtensionNoHandler = null;
protected override void InitializeVariables()
{
componentVariableMap["callflow$.strDestNo"] = new Variable("");
componentVariableMap["callflow$.strExtensionNo"] = new Variable("");
}
protected override void InitializeComponents()
{
Dictionary<string, Variable> variableMap = componentVariableMap;
{
SetExtDNDDest1962110077ECCComponent SetExtDNDDest = new SetExtDNDDest1962110077ECCComponent("SetExtDNDDest", callflow, myCall, logHeader);
SetExtDNDDest.Parameters.Add(new CallFlow.CFD.Parameter("strExtNr", () => { return variableMap["callflow$.strExtensionNo"].Value; }));
SetExtDNDDest.Parameters.Add(new CallFlow.CFD.Parameter("strDestNr", () => { return variableMap["callflow$.strDestNo"].Value; }));
mainFlowComponentList.Add(SetExtDNDDest);
ConditionalComponent CreateCondition1 = scope.CreateComponent<ConditionalComponent>("CreateCondition1");
mainFlowComponentList.Add(CreateCondition1);
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(SetExtDNDDest.ReturnValue,0)); });
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch1"));
TcxSetExtensionStatusComponent SetAvailable = scope.CreateComponent<TcxSetExtensionStatusComponent>("SetAvailable");
SetAvailable.ExtensionHandler = () => { return Convert.ToString(variableMap["callflow$.strExtensionNo"].Value); };
SetAvailable.ProfileNameHandler = () => { return "Available"; };
CreateCondition1.ContainerList[0].ComponentList.Add(SetAvailable);
PromptPlaybackComponent Weiterleitung_entfernt = scope.CreateComponent<PromptPlaybackComponent>("Weiterleitung_entfernt");
Weiterleitung_entfernt.AllowDtmfInput = true;
Weiterleitung_entfernt.Prompts.Add(new AudioFilePrompt(() => { return "Weiterleitung wurde entfernt.wav"; }));
CreateCondition1.ContainerList[0].ComponentList.Add(Weiterleitung_entfernt);
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(SetExtDNDDest.ReturnValue,4)); });
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch2"));
TcxSetExtensionStatusComponent SetExtCustom2 = scope.CreateComponent<TcxSetExtensionStatusComponent>("SetExtCustom2");
SetExtCustom2.ExtensionHandler = () => { return Convert.ToString(variableMap["callflow$.strExtensionNo"].Value); };
SetExtCustom2.ProfileNameHandler = () => { return "Custom 2"; };
CreateCondition1.ContainerList[1].ComponentList.Add(SetExtCustom2);
PromptPlaybackComponent PlayDigits = scope.CreateComponent<PromptPlaybackComponent>("PlayDigits");
PlayDigits.AllowDtmfInput = true;
PlayDigits.Prompts.Add(new AudioFilePrompt(() => { return "IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav"; }));
PlayDigits.Prompts.Add(new NumberPrompt(NumberPrompt.NumberFormats.OneByOne, () => { return Convert.ToString(variableMap["callflow$.strDestNo"].Value); }));
CreateCondition1.ContainerList[1].ComponentList.Add(PlayDigits);
}
{
}
{
}
}
public setStateAndFWDestination(OnlineServices onlineServices, OfficeHoursManager officeHoursManager,
CfdAppScope scope, string name, ICallflow callflow, ICall myCall, string logHeader) : base(name, callflow, myCall, logHeader)
{
this.onlineServices = onlineServices;
this.officeHoursManager = officeHoursManager;
this.scope = scope;
}
protected override void GetVariableValues()
{
if (_strDestNoHandler != null) componentVariableMap["callflow$.strDestNo"].Set(_strDestNoHandler());
if (_strExtensionNoHandler != null) componentVariableMap["callflow$.strExtensionNo"].Set(_strExtensionNoHandler());
}
public ObjectExpressionHandler strDestNoSetter { set { _strDestNoHandler = value; } }
public object strDestNo { get { return componentVariableMap["callflow$.strDestNo"].Value; } }
public ObjectExpressionHandler strExtensionNoSetter { set { _strExtensionNoHandler = value; } }
public object strExtensionNo { get { return componentVariableMap["callflow$.strExtensionNo"].Value; } }
private bool IsServerInHoliday(ICall myCall)
{
Tenant tenant = myCall.PS.GetTenant();
return tenant != null && tenant.IsHoliday(new DateTimeOffset(DateTime.Now));
}
private bool IsServerOfficeHourActive(ICall myCall)
{
Tenant tenant = myCall.PS.GetTenant();
if (tenant == null) return false;
string overrideOfficeTime = tenant.GetPropertyValue("OVERRIDEOFFICETIME");
if (!String.IsNullOrEmpty(overrideOfficeTime))
{
if (overrideOfficeTime == "1") // Forced to in office hours
return true;
else if (overrideOfficeTime == "2") // Forced to out of office hours
return false;
}
DateTime nowDt = DateTime.Now;
if (tenant.IsHoliday(new DateTimeOffset(nowDt))) return false;
Schedule officeHours = tenant.Hours;
Nullable<bool> result = officeHours.IsActiveTime(nowDt);
return result.GetValueOrDefault(false);
}
}
public class SetExtDNDDest1962110077ECCComponent : ExternalCodeExecutionComponent
{
public List<CallFlow.CFD.Parameter> Parameters { get; } = new List<CallFlow.CFD.Parameter>();
public SetExtDNDDest1962110077ECCComponent(string name, ICallflow callflow, ICall myCall, string projectName) : base(name, callflow, myCall, projectName) {}
protected override object ExecuteCode()
{
return SetExtDNDDest(Convert.ToString(Parameters[0].Value), Convert.ToString(Parameters[1].Value));
}
private object SetExtDNDDest(string strExtNr, string strDestNr)
{
var ext = PhoneSystem.Root.GetDNByNumber(strExtNr) as Extension;
var profile=ext.FwdProfiles.Where( x => x.Name == "Custom 2").First(); // 'Available', 'Away', 'Out of office', 'Custom 1', 'Custom 2', maybe parameter?
var NewStatus=-1;
if( profile != null ) {
// DestinationStruct need 3 parameter
// 1 DestinationType: 'None', 'VoiceMail', 'Extension', 'Queue', 'RingGroup', 'IVR', 'External', 'Fax', 'Boomerang' (external number),
// 'Deflect', 'VoiceMailOfDestination', 'Callback' (reserved), 'RoutePoint'
// 2 internal DN, maybe select by parameter - OR -
// 3 external number as string, maybe select by parameter
// DestinationType anhand der Zielnummer (strDestNr) bestimmen
bool isInternal = false;
bool isExtension = false;
bool isRG = false;
bool isIVR = false;
bool isQUEUE = false;
// check if strDestNr is a internal extension
var extension = PhoneSystem.Root.GetDNByNumber(strDestNr) as Extension;
if( extension != null ) {
isInternal = true;
isExtension = true;
}
// check if strDestNr is a ringgroup
var allRGs = PhoneSystem.Root.GetRingGroups();
foreach (var rg in allRGs)
{
if (rg.Number == strDestNr)
{
isInternal = true;
isRG = true;
break;
}
}
// check if strDestNr is a IVR/receptionist
var allIVRs = PhoneSystem.Root.GetIVRs();
foreach (var ivr in allIVRs)
{
if (ivr.Number == strDestNr)
{
isInternal = true;
isIVR = true;
break;
}
}
// check if strDestNr is a Queue
var allQUEUQs = PhoneSystem.Root.GetQueues();
foreach (var queue in allQUEUQs)
{
if (queue.Number == strDestNr)
{
isInternal = true;
isQUEUE = true;
break;
}
}
// define the DestinationStruct depending on the type of the destination target....
var dest = new DestinationStruct();
if (strExtNr.Equals(strDestNr)) {
// set state to available if destination number is the number of the extension
NewStatus = 0;
}
else {
if( isInternal) {
if( isExtension ) {
dest.To=DestinationType.Extension;
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
}
else if( isRG ) {
dest.To=DestinationType.RingGroup;
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
}
else if( isIVR ) {
dest.To=DestinationType.IVR;
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
}
else if( isQUEUE ) {
dest.To=DestinationType.Queue;
dest.Internal=PhoneSystem.Root.GetDNByNumber(strDestNr); // needed if internal destination
}
}
else {
dest.To=DestinationType.External;
dest.External=strDestNr; // needed if external destination
}
NewStatus = 4;
}
if( NewStatus >0)
{
// Depending on the type of status (present or absent), the forwarding must be entered in other target fields
if( profile.TypeOfRouting == RoutingType.Available) {
var route=profile.AvailableRoute; // maybe select by parameter?
route.NoAnswer.AllCalls = dest;
route.NoAnswer.Internal = dest;
route.Busy.AllCalls = route.NotRegistered.AllCalls = dest;
route.Busy.Internal = route.NotRegistered.Internal = dest;
}
if( profile.TypeOfRouting == RoutingType.Away) {
var route=profile.AwayRoute;
var external = profile.AwayRoute.External;
route.Internal.AllHours = dest; // maybe select by parameter?
route.Internal.OutOfOfficeHours = dest;
route.External.AllHours = dest;
route.External.OutOfOfficeHours = dest;
}
ext.Save();
}
}
return( NewStatus); }
}
}
}

View File

@ -0,0 +1,31 @@
## Dynamische Weiterleitungen ##
### 73_dynWeiterleitung_aus ###
Das Skript *73 schaltet die Nebenstelle auf den Status "Verfügbar" um um die Weiterleitung zu deaktivieren.
Nutzungsbeispiel: *73 anrufen um die aktuelle Nebenstelle in den Status Verfügbar umzuschalten (die Weiterleitung zu deaktivieren)
## Funktion auf BLF Tasten von Tischtelefonen legen ##
Die CFDs können ohne weitere Interaktion auf BLFs von Tischtelefonen provisioniert werden. Der Syntax ist vom Hersteller abhängig.
- Yealink BLF Tasten als indiv. Kurzwahl definieren :
z.B. Wtlg Handy: *72,0172123456#<br>
z.B. Wtlg Nst10: *72,10#<br>
z.B. Wtlg aus: *73<br>
- Fanvil BLF Tasten als indiv. Kurzwahl definieren :<br>
z.B. Wtlg Handy: *72,0172123456#<br>
z.B. Wtlg Nst10: *72,10#<br>
z.B. Wtlg aus: *73<br>
- Snom (ungetestet) BLF Tasten als indiv. Kurzwahl definieren:<br>
z.B. Wtlg Handy: *72;dtmf=0172123456#<br>
z.B. Wtlg Nst10: *72;dtmf=10#<br>
z.B. Wtlg aus: *73<br>
*Quellen / Nützliche Tools*
- [Ausgangspunkt war der Schnippsel von fxbastler aus dem 3CX Forum](https://www.3cx.de/forum/threads/rufweiterleitung.101354/page-2#post-430429)
- [Text-To-Speech German Vicky](https://ttsmp3.com/text-to-speech/German/)
- [g711.org mp3 zu wav konvertieren](https://g711.org/)
- [3CX Call Control API Doku](https://downloads-global.3cx.com/downloads/misc/callcontrolapi/3CXCallControlAPI_v20.zip)

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<File>
<Version>2.1</Version>
<Variables>
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Variable>
<Name>ExtensionNo</Name>
<ShowScopeProperty>false</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
<Variable>
<Name>ExtensionIVRPath</Name>
<ShowScopeProperty>false</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
<Variable>
<Name>wavfile</Name>
<ShowScopeProperty>false</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
<Variable>
<Name>wavfullpath</Name>
<ShowScopeProperty>false</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
</ArrayOfVariable>
</Variables>
<Flows>
<MainFlow>
<ns0:MainFlow Description="Callflow execution path." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e">
<ns0:UserComponent PublicProperties="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfUserProperty xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;UserProperty&gt;&lt;Name&gt;DialedExtensionNo&lt;/Name&gt;&lt;Value xsi:type=&quot;xsd:string&quot;&gt;&lt;/Value&gt;&lt;/UserProperty&gt;&lt;/ArrayOfUserProperty&gt;" RelativeFilePath="cGetDialedExtension.comp" Tag="" DebugModeActive="False" x:Name="cGetDialedExtension1" />
<ns0:VariableAssignmentComponent VariableName="callflow$.ExtensionNo" Tag="ExtensionNo" DebugModeActive="False" Expression="cGetDialedExtension1.DialedExtensionNo" x:Name="AssignVariable1" />
<ns0:VariableAssignmentComponent VariableName="callflow$.ExtensionIVRPath" Tag="ExtensionIVRPath" DebugModeActive="False" Expression="CONCATENATE(&quot;/var/lib/3cxpbx/Instance1/Data/Ivr/Voicemail/Data/&quot;,callflow$.ExtensionNo)" x:Name="AssignVariable2" />
<ns0:TcxGetExtensionStatusComponent Tag="" DebugModeActive="False" Extension="callflow$.ExtensionNo" x:Name="GetExtensionStatus1" />
<ns0:UserComponent PublicProperties="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfUserProperty xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;UserProperty&gt;&lt;Name&gt;Filename&lt;/Name&gt;&lt;/UserProperty&gt;&lt;UserProperty&gt;&lt;Name&gt;IVRPath&lt;/Name&gt;&lt;Value xsi:type=&quot;xsd:string&quot;&gt;callflow$.ExtensionIVRPath&lt;/Value&gt;&lt;/UserProperty&gt;&lt;UserProperty&gt;&lt;Name&gt;Profilename&lt;/Name&gt;&lt;Value xsi:type=&quot;xsd:string&quot;&gt;GetExtensionStatus1.CurrentProfileName&lt;/Value&gt;&lt;/UserProperty&gt;&lt;UserProperty&gt;&lt;Name&gt;result_Fullfilename&lt;/Name&gt;&lt;/UserProperty&gt;&lt;/ArrayOfUserProperty&gt;" RelativeFilePath="cGetVMWavfile.comp" Tag="" DebugModeActive="False" x:Name="cGetVMWavfile1" />
<ns0:PromptPlaybackComponent Tag="" AcceptDtmfInput="True" DebugModeActive="False" PromptList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfPrompt xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;Prompt xsi:type=&quot;DynamicAudioFilePrompt&quot;&gt;&lt;AudioFileName&gt;cGetVMWavfile1.result_Fullfilename&lt;/AudioFileName&gt;&lt;/Prompt&gt;&lt;/ArrayOfPrompt&gt;" x:Name="PromptPlayback2" />
<ns0:DisconnectCallComponent Tag="" DebugModeActive="False" x:Name="DisconnectCall1" />
</ns0:MainFlow>
</MainFlow>
<ErrorHandlerFlow>
<ns0:ErrorHandlerFlow Description="Execution path when an error ocurrs." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
</ErrorHandlerFlow>
<DisconnectHandlerFlow>
<ns0:DisconnectHandlerFlow Description="Execution path since the call gets disconnected." DebugModeActive="False" x:Name="Main" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
</DisconnectHandlerFlow>
</Flows>
</File>

View File

@ -0,0 +1,810 @@
using CallFlow.CFD;
using CallFlow;
using MimeKit;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks.Dataflow;
using System.Threading.Tasks;
using System.Threading;
using System;
using TCX.Configuration;
namespace VMNurAnsage
{
public class Main : ScriptBase<Main>, ICallflow, ICallflowProcessor
{
private bool executionStarted;
private bool executionFinished;
private bool disconnectFlowPending;
private BufferBlock<AbsEvent> eventBuffer;
private int currentComponentIndex;
private List<AbsComponent> mainFlowComponentList;
private List<AbsComponent> disconnectFlowComponentList;
private List<AbsComponent> errorFlowComponentList;
private List<AbsComponent> currentFlowComponentList;
private LogFormatter logFormatter;
private TimerManager timerManager;
private Dictionary<string, Variable> variableMap;
private TempWavFileManager tempWavFileManager;
private PromptQueue promptQueue;
private OnlineServices onlineServices;
private OfficeHoursManager officeHoursManager;
private CfdAppScope scope;
private void DisconnectCallAndExitCallflow()
{
if (currentFlowComponentList == disconnectFlowComponentList)
logFormatter.Trace("Callflow finished...");
else
{
logFormatter.Trace("Callflow finished, disconnecting call...");
MyCall.Terminate();
}
}
private async Task ExecuteErrorFlow()
{
if (currentFlowComponentList == errorFlowComponentList)
{
logFormatter.Trace("Error during error handler flow, exiting callflow...");
DisconnectCallAndExitCallflow();
}
else if (currentFlowComponentList == disconnectFlowComponentList)
{
logFormatter.Trace("Error during disconnect handler flow, exiting callflow...");
executionFinished = true;
}
else
{
currentFlowComponentList = errorFlowComponentList;
currentComponentIndex = 0;
if (errorFlowComponentList.Count > 0)
{
logFormatter.Trace("Start executing error handler flow...");
await ProcessStart();
}
else
{
logFormatter.Trace("Error handler flow is empty...");
DisconnectCallAndExitCallflow();
}
}
}
private async Task ExecuteDisconnectFlow()
{
currentFlowComponentList = disconnectFlowComponentList;
currentComponentIndex = 0;
disconnectFlowPending = false;
if (disconnectFlowComponentList.Count > 0)
{
logFormatter.Trace("Start executing disconnect handler flow...");
await ProcessStart();
}
else
{
logFormatter.Trace("Disconnect handler flow is empty...");
executionFinished = true;
}
}
private EventResults CheckEventResult(EventResults eventResult)
{
if (eventResult == EventResults.MoveToNextComponent && ++currentComponentIndex == currentFlowComponentList.Count)
{
DisconnectCallAndExitCallflow();
return EventResults.Exit;
}
else if (eventResult == EventResults.Exit)
DisconnectCallAndExitCallflow();
return eventResult;
}
private void InitializeVariables(string callID)
{
// Call variables
variableMap["session.ani"] = new Variable(MyCall.Caller.CallerID);
variableMap["session.callid"] = new Variable(callID);
variableMap["session.dnis"] = new Variable(MyCall.DN.Number);
variableMap["session.did"] = new Variable(MyCall.Caller.CalledNumber);
variableMap["session.audioFolder"] = new Variable(Path.Combine(RecordingManager.Instance.AudioFolder, promptQueue.ProjectAudioFolder));
variableMap["session.transferingExtension"] = new Variable(MyCall.ReferredByDN?.Number ?? string.Empty);
variableMap["session.forwardingExtension"] = new Variable(MyCall.OnBehalfOf?.Number ?? string.Empty);
// Standard variables
variableMap["RecordResult.NothingRecorded"] = new Variable(RecordComponent.RecordResults.NothingRecorded);
variableMap["RecordResult.StopDigit"] = new Variable(RecordComponent.RecordResults.StopDigit);
variableMap["RecordResult.Completed"] = new Variable(RecordComponent.RecordResults.Completed);
variableMap["MenuResult.Timeout"] = new Variable(MenuComponent.MenuResults.Timeout);
variableMap["MenuResult.InvalidOption"] = new Variable(MenuComponent.MenuResults.InvalidOption);
variableMap["MenuResult.ValidOption"] = new Variable(MenuComponent.MenuResults.ValidOption);
variableMap["UserInputResult.Timeout"] = new Variable(UserInputComponent.UserInputResults.Timeout);
variableMap["UserInputResult.InvalidDigits"] = new Variable(UserInputComponent.UserInputResults.InvalidDigits);
variableMap["UserInputResult.ValidDigits"] = new Variable(UserInputComponent.UserInputResults.ValidDigits);
variableMap["VoiceInputResult.Timeout"] = new Variable(VoiceInputComponent.VoiceInputResults.Timeout);
variableMap["VoiceInputResult.InvalidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.InvalidInput);
variableMap["VoiceInputResult.ValidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidInput);
variableMap["VoiceInputResult.ValidDtmfInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidDtmfInput);
// User variables
variableMap["callflow$.ExtensionNo"] = new Variable("");
variableMap["callflow$.ExtensionIVRPath"] = new Variable("");
variableMap["callflow$.wavfile"] = new Variable("");
variableMap["callflow$.wavfullpath"] = new Variable("");
variableMap["RecordResult.NothingRecorded"] = new Variable(RecordComponent.RecordResults.NothingRecorded);
variableMap["RecordResult.StopDigit"] = new Variable(RecordComponent.RecordResults.StopDigit);
variableMap["RecordResult.Completed"] = new Variable(RecordComponent.RecordResults.Completed);
variableMap["MenuResult.Timeout"] = new Variable(MenuComponent.MenuResults.Timeout);
variableMap["MenuResult.InvalidOption"] = new Variable(MenuComponent.MenuResults.InvalidOption);
variableMap["MenuResult.ValidOption"] = new Variable(MenuComponent.MenuResults.ValidOption);
variableMap["UserInputResult.Timeout"] = new Variable(UserInputComponent.UserInputResults.Timeout);
variableMap["UserInputResult.InvalidDigits"] = new Variable(UserInputComponent.UserInputResults.InvalidDigits);
variableMap["UserInputResult.ValidDigits"] = new Variable(UserInputComponent.UserInputResults.ValidDigits);
variableMap["VoiceInputResult.Timeout"] = new Variable(VoiceInputComponent.VoiceInputResults.Timeout);
variableMap["VoiceInputResult.InvalidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.InvalidInput);
variableMap["VoiceInputResult.ValidInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidInput);
variableMap["VoiceInputResult.ValidDtmfInput"] = new Variable(VoiceInputComponent.VoiceInputResults.ValidDtmfInput);
}
private void InitializeComponents(ICallflow callflow, ICall myCall, string logHeader)
{
scope = CfdModule.Instance.CreateScope(callflow, myCall, logHeader);
{
cGetDialedExtension cGetDialedExtension1 = new cGetDialedExtension(onlineServices, officeHoursManager, scope, "cGetDialedExtension1", callflow, myCall, logHeader);
mainFlowComponentList.Add(cGetDialedExtension1);
VariableAssignmentComponent AssignVariable1 = scope.CreateComponent<VariableAssignmentComponent>("AssignVariable1");
AssignVariable1.VariableName = "callflow$.ExtensionNo";
AssignVariable1.VariableValueHandler = () => { return cGetDialedExtension1.DialedExtensionNo; };
mainFlowComponentList.Add(AssignVariable1);
VariableAssignmentComponent AssignVariable2 = scope.CreateComponent<VariableAssignmentComponent>("AssignVariable2");
AssignVariable2.VariableName = "callflow$.ExtensionIVRPath";
AssignVariable2.VariableValueHandler = () => { return CFDFunctions.CONCATENATE(Convert.ToString("/var/lib/3cxpbx/Instance1/Data/Ivr/Voicemail/Data/"),Convert.ToString(variableMap["callflow$.ExtensionNo"].Value)); };
mainFlowComponentList.Add(AssignVariable2);
TcxGetExtensionStatusComponent GetExtensionStatus1 = scope.CreateComponent<TcxGetExtensionStatusComponent>("GetExtensionStatus1");
GetExtensionStatus1.ExtensionHandler = () => { return Convert.ToString(variableMap["callflow$.ExtensionNo"].Value); };
mainFlowComponentList.Add(GetExtensionStatus1);
cGetVMWavfile cGetVMWavfile1 = new cGetVMWavfile(onlineServices, officeHoursManager, scope, "cGetVMWavfile1", callflow, myCall, logHeader);
cGetVMWavfile1.IVRPathSetter = () => { return variableMap["callflow$.ExtensionIVRPath"].Value; };
cGetVMWavfile1.ProfilenameSetter = () => { return GetExtensionStatus1.CurrentProfileName; };
mainFlowComponentList.Add(cGetVMWavfile1);
PromptPlaybackComponent PromptPlayback2 = scope.CreateComponent<PromptPlaybackComponent>("PromptPlayback2");
PromptPlayback2.AllowDtmfInput = true;
PromptPlayback2.Prompts.Add(new AudioFilePrompt(() => { return Convert.ToString(cGetVMWavfile1.result_Fullfilename); }));
mainFlowComponentList.Add(PromptPlayback2);
DisconnectCallComponent DisconnectCall1 = scope.CreateComponent<DisconnectCallComponent>("DisconnectCall1");
mainFlowComponentList.Add(DisconnectCall1);
}
{
}
{
}
// Add a final DisconnectCall component to the main and error handler flows, in order to complete pending prompt playbacks...
DisconnectCallComponent mainAutoAddedFinalDisconnectCall = scope.CreateComponent<DisconnectCallComponent>("mainAutoAddedFinalDisconnectCall");
DisconnectCallComponent errorHandlerAutoAddedFinalDisconnectCall = scope.CreateComponent<DisconnectCallComponent>("errorHandlerAutoAddedFinalDisconnectCall");
mainFlowComponentList.Add(mainAutoAddedFinalDisconnectCall);
errorFlowComponentList.Add(errorHandlerAutoAddedFinalDisconnectCall);
}
public Main()
{
this.executionStarted = false;
this.executionFinished = false;
this.disconnectFlowPending = false;
this.eventBuffer = new BufferBlock<AbsEvent>();
this.currentComponentIndex = 0;
this.mainFlowComponentList = new List<AbsComponent>();
this.disconnectFlowComponentList = new List<AbsComponent>();
this.errorFlowComponentList = new List<AbsComponent>();
this.currentFlowComponentList = mainFlowComponentList;
this.timerManager = new TimerManager();
this.timerManager.OnTimeout += (state) => eventBuffer.Post(new TimeoutEvent(state));
this.variableMap = new Dictionary<string, Variable>();
AbsTextToSpeechEngine textToSpeechEngine = null;
AbsSpeechToTextEngine speechToTextEngine = null;
this.onlineServices = new OnlineServices(textToSpeechEngine, speechToTextEngine);
}
public override void Start()
{
string callID = MyCall?.Caller["chid"] ?? "Unknown";
string logHeader = $"VMNurAnsage - CallID {callID}";
this.logFormatter = new LogFormatter(MyCall, logHeader, "Callflow");
this.promptQueue = new PromptQueue(this, MyCall, "VMNurAnsage", logHeader);
this.tempWavFileManager = new TempWavFileManager(logFormatter);
this.timerManager.CallStarted();
this.officeHoursManager = new OfficeHoursManager(MyCall);
logFormatter.Info($"ConnectionStatus:`{MyCall.Status}`");
if (MyCall.Status == ConnectionStatus.Ringing)
MyCall.AssureMedia().ContinueWith(_ => StartInternal(logHeader, callID));
else
StartInternal(logHeader, callID);
}
private void StartInternal(string logHeader, string callID)
{
logFormatter.Trace("SetBackgroundAudio to false");
MyCall.SetBackgroundAudio(false, new string[] { });
logFormatter.Trace("Initialize components");
InitializeComponents(this, MyCall, logHeader);
logFormatter.Trace("Initialize variables");
InitializeVariables(callID);
MyCall.OnTerminated += () => eventBuffer.Post(new CallTerminatedEvent());
MyCall.OnDTMFInput += x => eventBuffer.Post(new DTMFReceivedEvent(x));
logFormatter.Trace("Start executing main flow...");
eventBuffer.Post(new StartEvent());
Task.Run(() => EventProcessingLoop());
}
public void PostStartEvent()
{
eventBuffer.Post(new StartEvent());
}
public void PostDTMFReceivedEvent(char digit)
{
eventBuffer.Post(new DTMFReceivedEvent(digit));
}
public void PostPromptPlayedEvent()
{
eventBuffer.Post(new PromptPlayedEvent());
}
public void PostTransferFailedEvent()
{
eventBuffer.Post(new TransferFailedEvent());
}
public void PostMakeCallResultEvent(bool result)
{
eventBuffer.Post(new MakeCallResultEvent(result));
}
public void PostCallTerminatedEvent()
{
eventBuffer.Post(new CallTerminatedEvent());
}
public void PostTimeoutEvent(object state)
{
eventBuffer.Post(new TimeoutEvent(state));
}
private async Task EventProcessingLoop()
{
executionStarted = true;
while (!executionFinished)
{
AbsEvent evt = await eventBuffer.ReceiveAsync();
await evt?.ProcessEvent(this);
}
if (scope != null) scope.Dispose();
}
public async Task ProcessStart()
{
try
{
EventResults eventResult;
do
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("Start executing component '" + currentComponent.Name + "'");
eventResult = await currentComponent.Start(timerManager, variableMap, tempWavFileManager, promptQueue);
}
while (CheckEventResult(eventResult) == EventResults.MoveToNextComponent);
if (eventResult == EventResults.Exit) executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessDTMFReceived(char digit)
{
try
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnDTMFReceived for component '" + currentComponent.Name + "' - Digit: '" + digit + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnDTMFReceived(timerManager, variableMap, tempWavFileManager, promptQueue, digit));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessPromptPlayed()
{
try
{
promptQueue.NotifyPlayFinished();
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnPromptPlayed for component '" + currentComponent.Name + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnPromptPlayed(timerManager, variableMap, tempWavFileManager, promptQueue));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessTransferFailed()
{
try
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnTransferFailed for component '" + currentComponent.Name + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnTransferFailed(timerManager, variableMap, tempWavFileManager, promptQueue));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessMakeCallResult(bool result)
{
try
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnMakeCallResult for component '" + currentComponent.Name + "' - Result: '" + result + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnMakeCallResult(timerManager, variableMap, tempWavFileManager, promptQueue, result));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
public async Task ProcessCallTerminated()
{
try
{
if (executionStarted)
{
// First notify the call termination to the current component
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnCallTerminated for component '" + currentComponent.Name + "'");
// Don't wrap around CheckEventResult, because the call has been already disconnected,
// and the following action to execute depends on the returned value.
EventResults eventResult = await currentComponent.OnCallTerminated(timerManager, variableMap, tempWavFileManager, promptQueue);
if (eventResult == EventResults.MoveToNextComponent)
{
// Next, if the current component has completed its job, execute the disconnect flow
await ExecuteDisconnectFlow();
}
else if (eventResult == EventResults.Wait)
{
// If the user component needs more events, wait for it to finish, and signal here that we need to execute
// the disconnect handler flow of the callflow next...
disconnectFlowPending = true;
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
finally
{
// Finally, delete temporary files
tempWavFileManager.DeleteFilesAndFolders();
}
}
public async Task ProcessTimeout(object state)
{
try
{
AbsComponent currentComponent = currentFlowComponentList[currentComponentIndex];
logFormatter.Trace("OnTimeout for component '" + currentComponent.Name + "'");
EventResults eventResult = CheckEventResult(await currentComponent.OnTimeout(timerManager, variableMap, tempWavFileManager, promptQueue, state));
if (eventResult == EventResults.MoveToNextComponent)
{
if (disconnectFlowPending)
await ExecuteDisconnectFlow();
else
await ProcessStart();
}
else if (eventResult == EventResults.Exit)
executionFinished = true;
}
catch (Exception exc)
{
logFormatter.Error("Error executing last component: " + exc.ToString());
await ExecuteErrorFlow();
}
}
// ------------------------------------------------------------------------------------------------------------
// User Defined component
// ------------------------------------------------------------------------------------------------------------
public class cGetDialedExtension : AbsUserComponent
{
private OnlineServices onlineServices;
private OfficeHoursManager officeHoursManager;
private CfdAppScope scope;
private ObjectExpressionHandler _DialedExtensionNoHandler = null;
protected override void InitializeVariables()
{
componentVariableMap["callflow$.DialedExtensionNo"] = new Variable("");
}
protected override void InitializeComponents()
{
Dictionary<string, Variable> variableMap = componentVariableMap;
{
ExecuteCSharpCode1864467505ECCComponent ExecuteCSharpCode1 = new ExecuteCSharpCode1864467505ECCComponent("ExecuteCSharpCode1", callflow, myCall, logHeader);
mainFlowComponentList.Add(ExecuteCSharpCode1);
VariableAssignmentComponent AssignVariable1 = scope.CreateComponent<VariableAssignmentComponent>("AssignVariable1");
AssignVariable1.VariableName = "callflow$.DialedExtensionNo";
AssignVariable1.VariableValueHandler = () => { return ExecuteCSharpCode1.ReturnValue; };
mainFlowComponentList.Add(AssignVariable1);
}
{
}
{
}
}
public cGetDialedExtension(OnlineServices onlineServices, OfficeHoursManager officeHoursManager,
CfdAppScope scope, string name, ICallflow callflow, ICall myCall, string logHeader) : base(name, callflow, myCall, logHeader)
{
this.onlineServices = onlineServices;
this.officeHoursManager = officeHoursManager;
this.scope = scope;
}
protected override void GetVariableValues()
{
if (_DialedExtensionNoHandler != null) componentVariableMap["callflow$.DialedExtensionNo"].Set(_DialedExtensionNoHandler());
}
public ObjectExpressionHandler DialedExtensionNoSetter { set { _DialedExtensionNoHandler = value; } }
public object DialedExtensionNo { get { return componentVariableMap["callflow$.DialedExtensionNo"].Value; } }
private bool IsServerInHoliday(ICall myCall)
{
Tenant tenant = myCall.PS.GetTenant();
return tenant != null && tenant.IsHoliday(new DateTimeOffset(DateTime.Now));
}
private bool IsServerOfficeHourActive(ICall myCall)
{
Tenant tenant = myCall.PS.GetTenant();
if (tenant == null) return false;
string overrideOfficeTime = tenant.GetPropertyValue("OVERRIDEOFFICETIME");
if (!String.IsNullOrEmpty(overrideOfficeTime))
{
if (overrideOfficeTime == "1") // Forced to in office hours
return true;
else if (overrideOfficeTime == "2") // Forced to out of office hours
return false;
}
DateTime nowDt = DateTime.Now;
if (tenant.IsHoliday(new DateTimeOffset(nowDt))) return false;
Schedule officeHours = tenant.Hours;
Nullable<bool> result = officeHours.IsActiveTime(nowDt);
return result.GetValueOrDefault(false);
}
}
public class ExecuteCSharpCode1864467505ECCComponent : ExternalCodeExecutionComponent
{
public List<CallFlow.CFD.Parameter> Parameters { get; } = new List<CallFlow.CFD.Parameter>();
public ExecuteCSharpCode1864467505ECCComponent(string name, ICallflow callflow, ICall myCall, string projectName) : base(name, callflow, myCall, projectName) {}
protected override object ExecuteCode()
{
return test();
}
private object test()
{
// https://www.3cx.com/community/threads/get-extension-by-called-number.132441/post-630907
string retval="0";
if(myCall.Caller.DN is ExternalLine externalLine && myCall.IsInbound)
{
string[] range;
foreach (var a in externalLine.RoutingRules)
{
bool match = (a.Conditions.Condition.Type == RuleConditionType.BasedOnDID &&
(
a.Data == myCall.Caller.CalledNumber
|| (a.Data.StartsWith('*') && myCall.Caller.CalledNumber.EndsWith(a.Data[1..]))
))
||
(
a.Conditions.Condition.Type == RuleConditionType.BasedOnCallerID &&
a.Data.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Any
(x =>
x == "*"
|| x == myCall.Caller.CallerID
|| x.StartsWith('*') && myCall.Caller.CallerID.EndsWith(x[1..])
|| x.EndsWith('*') && myCall.Caller.CallerID.StartsWith(x[..^1])
|| x.StartsWith('*') && x.EndsWith('*') && myCall.Caller.CallerID.Contains(x[1..^1])
|| ((range = x.Split("-", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)).Length == 2 ?
(range[0].Length == myCall.Caller.CallerID.Length && range[1].Length == myCall.Caller.CallerID.Length) && (range[0].CompareTo(myCall.Caller.CallerID) <= 0 && range[1].CompareTo(myCall.Caller.CallerID) >= 0) : false)
)
)
||
(a.Conditions.Condition.Type == RuleConditionType.ForwardAll);
if (match)
{
retval=(a.ForwardDestinations.OfficeHoursDestination).Internal?.Number?? "0";
break;
}
}
}
else
{
retval=myCall.Caller.AttachedData["extnumber"]?? "0";
}
return retval; }
}
// ------------------------------------------------------------------------------------------------------------
// User Defined component
// ------------------------------------------------------------------------------------------------------------
public class cGetVMWavfile : AbsUserComponent
{
private OnlineServices onlineServices;
private OfficeHoursManager officeHoursManager;
private CfdAppScope scope;
private ObjectExpressionHandler _IVRPathHandler = null;
private ObjectExpressionHandler _ProfilenameHandler = null;
private ObjectExpressionHandler _result_FullfilenameHandler = null;
private ObjectExpressionHandler _wavDefaultHandler = null;
private ObjectExpressionHandler _wavAvailableHandler = null;
private ObjectExpressionHandler _wavAwayHandler = null;
private ObjectExpressionHandler _wavOoOHandler = null;
private ObjectExpressionHandler _wavCustom1Handler = null;
private ObjectExpressionHandler _wavCustom2Handler = null;
private ObjectExpressionHandler _FilenameHandler = null;
protected override void InitializeVariables()
{
componentVariableMap["callflow$.IVRPath"] = new Variable("");
componentVariableMap["callflow$.Profilename"] = new Variable("");
componentVariableMap["callflow$.result_Fullfilename"] = new Variable("");
componentVariableMap["callflow$.wavDefault"] = new Variable("");
componentVariableMap["callflow$.wavAvailable"] = new Variable("");
componentVariableMap["callflow$.wavAway"] = new Variable("");
componentVariableMap["callflow$.wavOoO"] = new Variable("");
componentVariableMap["callflow$.wavCustom1"] = new Variable("");
componentVariableMap["callflow$.wavCustom2"] = new Variable("");
componentVariableMap["callflow$.Filename"] = new Variable("");
}
protected override void InitializeComponents()
{
Dictionary<string, Variable> variableMap = componentVariableMap;
{
FileManagementComponent ReadWriteFile1 = scope.CreateComponent<FileManagementComponent>("ReadWriteFile1");
ReadWriteFile1.Action = FileManagementComponent.Actions.Read;
ReadWriteFile1.FileMode = System.IO.FileMode.Open;
ReadWriteFile1.FileNameHandler = () => { return Convert.ToString(CFDFunctions.CONCATENATE(Convert.ToString(variableMap["callflow$.IVRPath"].Value),Convert.ToString("/greetings.xml"))); };
ReadWriteFile1.FirstLineToReadHandler = () => { return Convert.ToInt32(0); };
ReadWriteFile1.ReadToEndHandler = () => { return Convert.ToBoolean(true); };
mainFlowComponentList.Add(ReadWriteFile1);
TextAnalyzerComponent JsonXmlParser1 = scope.CreateComponent<TextAnalyzerComponent>("JsonXmlParser1");
JsonXmlParser1.TextType = TextAnalyzerComponent.TextTypes.XML;
JsonXmlParser1.TextHandler = () => { return Convert.ToString(ReadWriteFile1.Result); };
JsonXmlParser1.Mappings.Add("string(/overrides/greeting[@profile='default']/@file)", "callflow$.wavDefault");
JsonXmlParser1.Mappings.Add("string(/overrides/greeting[@profile='Available']/@file)", "callflow$.wavAvailable");
JsonXmlParser1.Mappings.Add("string(/overrides/greeting[@profile='Away']/@file)", "callflow$.wavAway");
JsonXmlParser1.Mappings.Add("string(/overrides/greeting[@profile='Out of office']/@file)", "callflow$.wavOoO");
JsonXmlParser1.Mappings.Add("string(/overrides/greeting[@profile='Custom 1']/@file)", "callflow$.wavCustom1");
JsonXmlParser1.Mappings.Add("string(/overrides/greeting[@profile='Custom 2']/@file)", "callflow$.wavCustom2");
mainFlowComponentList.Add(JsonXmlParser1);
ConditionalComponent CreateCondition1 = scope.CreateComponent<ConditionalComponent>("CreateCondition1");
mainFlowComponentList.Add(CreateCondition1);
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["callflow$.Profilename"].Value,"Available")); });
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("cond1"));
VariableAssignmentComponent AssignVariable1 = scope.CreateComponent<VariableAssignmentComponent>("AssignVariable1");
AssignVariable1.VariableName = "callflow$.Filename";
AssignVariable1.VariableValueHandler = () => { return variableMap["callflow$.wavAvailable"].Value; };
CreateCondition1.ContainerList[0].ComponentList.Add(AssignVariable1);
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["callflow$.Profilename"].Value,"Away")); });
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch1"));
VariableAssignmentComponent AssignVariable2 = scope.CreateComponent<VariableAssignmentComponent>("AssignVariable2");
AssignVariable2.VariableName = "callflow$.Filename";
AssignVariable2.VariableValueHandler = () => { return variableMap["callflow$.wavAway"].Value; };
CreateCondition1.ContainerList[1].ComponentList.Add(AssignVariable2);
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["callflow$.Profilename"].Value,"Out of office")); });
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch2"));
VariableAssignmentComponent AssignVariable3 = scope.CreateComponent<VariableAssignmentComponent>("AssignVariable3");
AssignVariable3.VariableName = "callflow$.Filename";
AssignVariable3.VariableValueHandler = () => { return variableMap["callflow$.wavOoO"].Value; };
CreateCondition1.ContainerList[2].ComponentList.Add(AssignVariable3);
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["callflow$.Profilename"].Value,"Custom 1")); });
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch3"));
VariableAssignmentComponent AssignVariable4 = scope.CreateComponent<VariableAssignmentComponent>("AssignVariable4");
AssignVariable4.VariableName = "callflow$.Filename";
AssignVariable4.VariableValueHandler = () => { return variableMap["callflow$.wavCustom1"].Value; };
CreateCondition1.ContainerList[3].ComponentList.Add(AssignVariable4);
CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["callflow$.Profilename"].Value,"Custom 2")); });
CreateCondition1.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch4"));
VariableAssignmentComponent AssignVariable5 = scope.CreateComponent<VariableAssignmentComponent>("AssignVariable5");
AssignVariable5.VariableName = "callflow$.Filename";
AssignVariable5.VariableValueHandler = () => { return variableMap["callflow$.wavCustom2"].Value; };
CreateCondition1.ContainerList[4].ComponentList.Add(AssignVariable5);
ConditionalComponent CreateCondition2 = scope.CreateComponent<ConditionalComponent>("CreateCondition2");
mainFlowComponentList.Add(CreateCondition2);
CreateCondition2.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["callflow$.Filename"].Value,"")); });
CreateCondition2.ContainerList.Add(scope.CreateComponent<SequenceContainerComponent>("conditionalComponentBranch5"));
VariableAssignmentComponent variableAssignmentComponent1 = scope.CreateComponent<VariableAssignmentComponent>("variableAssignmentComponent1");
variableAssignmentComponent1.VariableName = "callflow$.Filename";
variableAssignmentComponent1.VariableValueHandler = () => { return variableMap["callflow$.wavDefault"].Value; };
CreateCondition2.ContainerList[0].ComponentList.Add(variableAssignmentComponent1);
VariableAssignmentComponent AssignVariable6 = scope.CreateComponent<VariableAssignmentComponent>("AssignVariable6");
AssignVariable6.VariableName = "callflow$.result_Fullfilename";
AssignVariable6.VariableValueHandler = () => { return CFDFunctions.CONCATENATE(Convert.ToString(variableMap["callflow$.IVRPath"].Value),Convert.ToString("/"),Convert.ToString(variableMap["callflow$.Filename"].Value)); };
mainFlowComponentList.Add(AssignVariable6);
}
{
}
{
}
}
public cGetVMWavfile(OnlineServices onlineServices, OfficeHoursManager officeHoursManager,
CfdAppScope scope, string name, ICallflow callflow, ICall myCall, string logHeader) : base(name, callflow, myCall, logHeader)
{
this.onlineServices = onlineServices;
this.officeHoursManager = officeHoursManager;
this.scope = scope;
}
protected override void GetVariableValues()
{
if (_IVRPathHandler != null) componentVariableMap["callflow$.IVRPath"].Set(_IVRPathHandler());
if (_ProfilenameHandler != null) componentVariableMap["callflow$.Profilename"].Set(_ProfilenameHandler());
if (_result_FullfilenameHandler != null) componentVariableMap["callflow$.result_Fullfilename"].Set(_result_FullfilenameHandler());
if (_wavDefaultHandler != null) componentVariableMap["callflow$.wavDefault"].Set(_wavDefaultHandler());
if (_wavAvailableHandler != null) componentVariableMap["callflow$.wavAvailable"].Set(_wavAvailableHandler());
if (_wavAwayHandler != null) componentVariableMap["callflow$.wavAway"].Set(_wavAwayHandler());
if (_wavOoOHandler != null) componentVariableMap["callflow$.wavOoO"].Set(_wavOoOHandler());
if (_wavCustom1Handler != null) componentVariableMap["callflow$.wavCustom1"].Set(_wavCustom1Handler());
if (_wavCustom2Handler != null) componentVariableMap["callflow$.wavCustom2"].Set(_wavCustom2Handler());
if (_FilenameHandler != null) componentVariableMap["callflow$.Filename"].Set(_FilenameHandler());
}
public ObjectExpressionHandler IVRPathSetter { set { _IVRPathHandler = value; } }
public object IVRPath { get { return componentVariableMap["callflow$.IVRPath"].Value; } }
public ObjectExpressionHandler ProfilenameSetter { set { _ProfilenameHandler = value; } }
public object Profilename { get { return componentVariableMap["callflow$.Profilename"].Value; } }
public ObjectExpressionHandler result_FullfilenameSetter { set { _result_FullfilenameHandler = value; } }
public object result_Fullfilename { get { return componentVariableMap["callflow$.result_Fullfilename"].Value; } }
public ObjectExpressionHandler wavDefaultSetter { set { _wavDefaultHandler = value; } }
public object wavDefault { get { return componentVariableMap["callflow$.wavDefault"].Value; } }
public ObjectExpressionHandler wavAvailableSetter { set { _wavAvailableHandler = value; } }
public object wavAvailable { get { return componentVariableMap["callflow$.wavAvailable"].Value; } }
public ObjectExpressionHandler wavAwaySetter { set { _wavAwayHandler = value; } }
public object wavAway { get { return componentVariableMap["callflow$.wavAway"].Value; } }
public ObjectExpressionHandler wavOoOSetter { set { _wavOoOHandler = value; } }
public object wavOoO { get { return componentVariableMap["callflow$.wavOoO"].Value; } }
public ObjectExpressionHandler wavCustom1Setter { set { _wavCustom1Handler = value; } }
public object wavCustom1 { get { return componentVariableMap["callflow$.wavCustom1"].Value; } }
public ObjectExpressionHandler wavCustom2Setter { set { _wavCustom2Handler = value; } }
public object wavCustom2 { get { return componentVariableMap["callflow$.wavCustom2"].Value; } }
public ObjectExpressionHandler FilenameSetter { set { _FilenameHandler = value; } }
public object Filename { get { return componentVariableMap["callflow$.Filename"].Value; } }
private bool IsServerInHoliday(ICall myCall)
{
Tenant tenant = myCall.PS.GetTenant();
return tenant != null && tenant.IsHoliday(new DateTimeOffset(DateTime.Now));
}
private bool IsServerOfficeHourActive(ICall myCall)
{
Tenant tenant = myCall.PS.GetTenant();
if (tenant == null) return false;
string overrideOfficeTime = tenant.GetPropertyValue("OVERRIDEOFFICETIME");
if (!String.IsNullOrEmpty(overrideOfficeTime))
{
if (overrideOfficeTime == "1") // Forced to in office hours
return true;
else if (overrideOfficeTime == "2") // Forced to out of office hours
return false;
}
DateTime nowDt = DateTime.Now;
if (tenant.IsHoliday(new DateTimeOffset(nowDt))) return false;
Schedule officeHours = tenant.Hours;
Nullable<bool> result = officeHours.IsActiveTime(nowDt);
return result.GetValueOrDefault(false);
}
}
}
}

Binary file not shown.

View File

@ -0,0 +1,10 @@
# 3CX
### 99_Mailbox_NurAnsage ###
Das Skript *99 stellt eine Ansage ohne Aufnahmemöglichkeit zur Verfügung, die der Nutzer (im Gegensatz zur IVR Lösung) selbst besprechen/verändern kann.
Der Aufruf erfolgt als Ziel über die Anrufweiterleitung in der Nebenstelle. Die Ansagen werden aus den im Status des Benutzers hinterlegten Ansagen bezogen.
Ist dort keine gefüllt, so erfolgt ein Fallback auf die Standardansage (Register Mailbox).
Da in v20 die ursprünglich angerufene Nebenstelle als Quelle der Ansagen nur indirekt mit schwarzer Magie ermittelt werden kann,
kann dieses CFA nur bei direkter Weiterleitung aus der Nebenstelle (10 -> *99) verwendet werden. Also kein Nebenstellenhopping (10->11->*99), nicht als IVR Ziel, ...)

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<Graphical_Application_Designer_Project>
<Version>2.1</Version>
<Files>
<File path="cGetDialedExtension.comp" type="component" />
<File path="cGetVMWavfile.comp" type="component" />
<File path="Main.flow" type="callflow" />
</Files>
<Variables>
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
</Variables>
<DebugBuildSuccessful>False</DebugBuildSuccessful>
<ReleaseBuildSuccessful>True</ReleaseBuildSuccessful>
<DebugBuildNumber>0</DebugBuildNumber>
<ReleaseBuildNumber>43</ReleaseBuildNumber>
<ChangedSinceLastDebugBuild>True</ChangedSinceLastDebugBuild>
<DoNotAskForExtension>True</DoNotAskForExtension>
<Extension>*99</Extension>
<OnlineServicesTextToSpeechEngine>None</OnlineServicesTextToSpeechEngine>
<OnlineServicesSpeechToTextEngine>None</OnlineServicesSpeechToTextEngine>
<AmazonClientID>
</AmazonClientID>
<AmazonClientSecret>
</AmazonClientSecret>
<AmazonRegion>us-east-2</AmazonRegion>
<AmazonLexicons>
</AmazonLexicons>
<GoogleCloudServiceAccountKeyFileName>
</GoogleCloudServiceAccountKeyFileName>
<GoogleCloudServiceAccountKeyJSON>
</GoogleCloudServiceAccountKeyJSON>
</Graphical_Application_Designer_Project>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<File>
<Version>2.1</Version>
<Variables>
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Variable>
<Name>DialedExtensionNo</Name>
<ShowScopeProperty>true</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
</ArrayOfVariable>
</Variables>
<Flows>
<MainFlow>
<ns0:MainFlow Description="Callflow execution path." DebugModeActive="False" x:Name="cGetDialedExtension" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e">
<ns0:ExecuteCSharpCodeComponent ReturnsValue="True" Code="// https://www.3cx.com/community/threads/get-extension-by-called-number.132441/post-630907&#xD;&#xA;string retval=&quot;0&quot;;&#xD;&#xA;if(myCall.Caller.DN is ExternalLine externalLine &amp;&amp; myCall.IsInbound)&#xD;&#xA;{&#xD;&#xA; string[] range;&#xD;&#xA; foreach (var a in externalLine.RoutingRules)&#xD;&#xA; {&#xD;&#xA; bool match = (a.Conditions.Condition.Type == RuleConditionType.BasedOnDID &amp;&amp;&#xD;&#xA; (&#xD;&#xA; a.Data == myCall.Caller.CalledNumber&#xD;&#xA; || (a.Data.StartsWith('*') &amp;&amp; myCall.Caller.CalledNumber.EndsWith(a.Data[1..]))&#xD;&#xA; ))&#xD;&#xA; ||&#xD;&#xA; (&#xD;&#xA; a.Conditions.Condition.Type == RuleConditionType.BasedOnCallerID &amp;&amp;&#xD;&#xA; a.Data.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)&#xD;&#xA; .Any&#xD;&#xA; (x =&gt;&#xD;&#xA; x == &quot;*&quot;&#xD;&#xA; || x == myCall.Caller.CallerID&#xD;&#xA; || x.StartsWith('*') &amp;&amp; myCall.Caller.CallerID.EndsWith(x[1..])&#xD;&#xA; || x.EndsWith('*') &amp;&amp; myCall.Caller.CallerID.StartsWith(x[..^1])&#xD;&#xA; || x.StartsWith('*') &amp;&amp; x.EndsWith('*') &amp;&amp; myCall.Caller.CallerID.Contains(x[1..^1])&#xD;&#xA; || ((range = x.Split(&quot;-&quot;, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)).Length == 2 ?&#xD;&#xA; (range[0].Length == myCall.Caller.CallerID.Length &amp;&amp; range[1].Length == myCall.Caller.CallerID.Length) &amp;&amp; (range[0].CompareTo(myCall.Caller.CallerID) &lt;= 0 &amp;&amp; range[1].CompareTo(myCall.Caller.CallerID) &gt;= 0) : false)&#xD;&#xA; )&#xD;&#xA; )&#xD;&#xA; ||&#xD;&#xA; (a.Conditions.Condition.Type == RuleConditionType.ForwardAll);&#xD;&#xA; &#xD;&#xA; if (match)&#xD;&#xA; {&#xD;&#xA; retval=(a.ForwardDestinations.OfficeHoursDestination).Internal?.Number?? &quot;0&quot;;&#xD;&#xA; break;&#xD;&#xA; }&#xD;&#xA; }&#xD;&#xA;}&#xD;&#xA;else&#xD;&#xA;{&#xD;&#xA; retval=myCall.Caller.AttachedData[&quot;extnumber&quot;]?? &quot;0&quot;;&#xD;&#xA;}&#xD;&#xA;return retval;" ParameterList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfScriptParameter xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; /&gt;" MethodName="test" Tag="" DebugModeActive="False" x:Name="ExecuteCSharpCode1" />
<ns0:VariableAssignmentComponent VariableName="callflow$.DialedExtensionNo" Tag="" DebugModeActive="False" Expression="ExecuteCSharpCode1.ReturnValue" x:Name="AssignVariable1" />
</ns0:MainFlow>
</MainFlow>
<ErrorHandlerFlow>
<ns0:ErrorHandlerFlow Description="Execution path when an error ocurrs." DebugModeActive="False" x:Name="cGetDialedExtension" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
</ErrorHandlerFlow>
<DisconnectHandlerFlow>
<ns0:DisconnectHandlerFlow Description="Execution path since the call gets disconnected." DebugModeActive="False" x:Name="cGetDialedExtension" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
</DisconnectHandlerFlow>
</Flows>
</File>

View File

@ -0,0 +1,112 @@
<?xml version="1.0" encoding="utf-8"?>
<File>
<Version>2.1</Version>
<Variables>
<ArrayOfVariable xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Variable>
<Name>IVRPath</Name>
<ShowScopeProperty>true</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
<Variable>
<Name>Profilename</Name>
<ShowScopeProperty>true</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
<Variable>
<Name>result_Fullfilename</Name>
<ShowScopeProperty>true</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
<Variable>
<Name>wavDefault</Name>
<Scope>Private</Scope>
<ShowScopeProperty>true</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
<Variable>
<Name>wavAvailable</Name>
<Scope>Private</Scope>
<ShowScopeProperty>true</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
<Variable>
<Name>wavAway</Name>
<Scope>Private</Scope>
<ShowScopeProperty>true</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
<Variable>
<Name>wavOoO</Name>
<Scope>Private</Scope>
<ShowScopeProperty>true</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
<Variable>
<Name>wavCustom1</Name>
<Scope>Private</Scope>
<ShowScopeProperty>true</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
<Variable>
<Name>wavCustom2</Name>
<Scope>Private</Scope>
<ShowScopeProperty>true</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
<Variable>
<Name>Filename</Name>
<Scope>Private</Scope>
<ShowScopeProperty>true</ShowScopeProperty>
<DebuggerVisible>true</DebuggerVisible>
<HelpText />
</Variable>
</ArrayOfVariable>
</Variables>
<Flows>
<MainFlow>
<ns0:MainFlow Description="Callflow execution path." DebugModeActive="False" x:Name="cGetVMWavfile" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e">
<ns0:FileManagementComponent AppendFinalCrLf="True" Action="Read" x:Name="ReadWriteFile1" FileName="CONCATENATE(callflow$.IVRPath,&quot;/greetings.xml&quot;)" ReadToEnd="true" Content="" Tag="Greetings.xml" FirstLineToRead="0" OpenMode="Open" DebugModeActive="False" LinesToRead="" />
<ns0:JsonXmlParserComponent ResponseMappingsList="&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-16&quot;?&gt;&lt;ArrayOfResponseMapping xmlns:xsd=&quot;http://www.w3.org/2001/XMLSchema&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;&lt;ResponseMapping&gt;&lt;Path&gt;string(/overrides/greeting[@profile='default']/@file)&lt;/Path&gt;&lt;Variable&gt;callflow$.wavDefault&lt;/Variable&gt;&lt;/ResponseMapping&gt;&lt;ResponseMapping&gt;&lt;Path&gt;string(/overrides/greeting[@profile='Available']/@file)&lt;/Path&gt;&lt;Variable&gt;callflow$.wavAvailable&lt;/Variable&gt;&lt;/ResponseMapping&gt;&lt;ResponseMapping&gt;&lt;Path&gt;string(/overrides/greeting[@profile='Away']/@file)&lt;/Path&gt;&lt;Variable&gt;callflow$.wavAway&lt;/Variable&gt;&lt;/ResponseMapping&gt;&lt;ResponseMapping&gt;&lt;Path&gt;string(/overrides/greeting[@profile='Out of office']/@file)&lt;/Path&gt;&lt;Variable&gt;callflow$.wavOoO&lt;/Variable&gt;&lt;/ResponseMapping&gt;&lt;ResponseMapping&gt;&lt;Path&gt;string(/overrides/greeting[@profile='Custom 1']/@file)&lt;/Path&gt;&lt;Variable&gt;callflow$.wavCustom1&lt;/Variable&gt;&lt;/ResponseMapping&gt;&lt;ResponseMapping&gt;&lt;Path&gt;string(/overrides/greeting[@profile='Custom 2']/@file)&lt;/Path&gt;&lt;Variable&gt;callflow$.wavCustom2&lt;/Variable&gt;&lt;/ResponseMapping&gt;&lt;/ArrayOfResponseMapping&gt;" Input="ReadWriteFile1.Result" Tag="VMdefaultWav" DebugModeActive="False" TextType="XML" x:Name="JsonXmlParser1" />
<ns0:ConditionalComponent Tag="" DebugModeActive="False" x:Name="CreateCondition1">
<ns0:ConditionalComponentBranch Condition="EQUAL(callflow$.Profilename,&quot;Available&quot;)" Description="Execution path when the specified condition is met." Tag="Available" DebugModeActive="False" x:Name="cond1">
<ns0:VariableAssignmentComponent VariableName="callflow$.Filename" Tag="filename" DebugModeActive="False" Expression="callflow$.wavAvailable" x:Name="AssignVariable1" />
</ns0:ConditionalComponentBranch>
<ns0:ConditionalComponentBranch Condition="EQUAL(callflow$.Profilename,&quot;Away&quot;)" Description="Execution path when the specified condition is met." Tag="Away" DebugModeActive="False" x:Name="conditionalComponentBranch1">
<ns0:VariableAssignmentComponent VariableName="callflow$.Filename" Tag="filename" DebugModeActive="False" Expression="callflow$.wavAway" x:Name="AssignVariable2" />
</ns0:ConditionalComponentBranch>
<ns0:ConditionalComponentBranch Condition="EQUAL(callflow$.Profilename,&quot;Out of office&quot;)" Description="Execution path when the specified condition is met." Tag="Available" DebugModeActive="False" x:Name="conditionalComponentBranch2">
<ns0:VariableAssignmentComponent VariableName="callflow$.Filename" Tag="filename" DebugModeActive="False" Expression="callflow$.wavOoO" x:Name="AssignVariable3" />
</ns0:ConditionalComponentBranch>
<ns0:ConditionalComponentBranch Condition="EQUAL(callflow$.Profilename,&quot;Custom 1&quot;)" Description="Execution path when the specified condition is met." Tag="Available" DebugModeActive="False" x:Name="conditionalComponentBranch3">
<ns0:VariableAssignmentComponent VariableName="callflow$.Filename" Tag="filename" DebugModeActive="False" Expression="callflow$.wavCustom1" x:Name="AssignVariable4" />
</ns0:ConditionalComponentBranch>
<ns0:ConditionalComponentBranch Condition="EQUAL(callflow$.Profilename,&quot;Custom 2&quot;)" Description="Execution path when the specified condition is met." Tag="Available" DebugModeActive="False" x:Name="conditionalComponentBranch4">
<ns0:VariableAssignmentComponent VariableName="callflow$.Filename" Tag="filename" DebugModeActive="False" Expression="callflow$.wavCustom2" x:Name="AssignVariable5" />
</ns0:ConditionalComponentBranch>
</ns0:ConditionalComponent>
<ns0:ConditionalComponent Tag="" DebugModeActive="False" x:Name="CreateCondition2">
<ns0:ConditionalComponentBranch Condition="EQUAL(callflow$.Filename,&quot;&quot;)" Description="Execution path when the specified condition is met." Tag="" DebugModeActive="False" x:Name="conditionalComponentBranch5">
<ns0:VariableAssignmentComponent VariableName="callflow$.Filename" Tag="filename" DebugModeActive="False" Expression="callflow$.wavDefault" x:Name="variableAssignmentComponent1" />
</ns0:ConditionalComponentBranch>
</ns0:ConditionalComponent>
<ns0:VariableAssignmentComponent VariableName="callflow$.result_Fullfilename" Tag="result_FullFilename" DebugModeActive="False" Expression="CONCATENATE(callflow$.IVRPath,&quot;/&quot;,callflow$.Filename)" x:Name="AssignVariable6" />
</ns0:MainFlow>
</MainFlow>
<ErrorHandlerFlow>
<ns0:ErrorHandlerFlow Description="Execution path when an error ocurrs." DebugModeActive="False" x:Name="cGetVMWavfile" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
</ErrorHandlerFlow>
<DisconnectHandlerFlow>
<ns0:DisconnectHandlerFlow Description="Execution path since the call gets disconnected." DebugModeActive="False" x:Name="cGetVMWavfile" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ns0="clr-namespace:TCX.CFD.Classes.Components;Assembly=3CX Call Flow Designer, Version=20.2.84.0, Culture=neutral, PublicKeyToken=7cb95a1a133e706e" />
</DisconnectHandlerFlow>
</Flows>
</File>

View File

@ -1,2 +1,71 @@
# 3CX_CFD
Die importierebaren ZIP Dateien liegen im Ordner /RELEASE
## Profilstatus wechseln ##
### 39_Profilstatus_mitExtension ###
Das Skript *39 ermöglicht das wechseln des Profilstatus einer beliebigen Nebenstelle
Mittels DTMF wird zuerst die Nebenstelle abgefragt und dann die ID des Status (0-4, wobei 0 = Verfügbar).
Bei Tischtelefonen kann dies wie unten beschrieben auf BLF Tasten gelegt werden.
Bei Apps und DECT Telefonen kann die Zielrufnummer nur über die Telefontastatur eingegeben werden.
Nutzungsbeispiel: *39 anrufen, "40#2#" eingeben um für die Nebenstelle 40 auf "Bitte nicht stören umzuschalten.
## 99_Mailbox_NurAnsage ##
Das Skript *99 stellt eine Ansage ohne Aufnahmemöglichkeit zur Verfügung, die der Nutzer (im Gegensatz zur IVR Lösung) selbst besprechen/verändern kann.
Der Aufruf erfolgt als Ziel über die Anrufweiterleitung in der Nebenstelle.
Die Ansagen werden aus den im Status des Benutzers hinterlegten Ansagen bezogen.
Ist dort keine gefüllt, so erfolgt ein Fallback auf die Standardansage (Register Mailbox).
Da in v20 die ursprünglich angerufene Nebenstelle als Quelle der Ansagen nur indirekt mit schwarzer Magie ermittelt werden kann,
kann dieses CFA nur bei direkter Weiterleitung aus der Nebenstelle (10 -> *99) verwendet werden. Also kein Nebenstellenhopping (10->11->*99), nicht als IVR Ziel, ...)
## Dynamische Weiterleitungen ##
### 72_dynWeiterleitung_an ###
Das Skript *72 fragt mittels DTFM die Zielrufnummer der Weiterleitung ab, trägt diese in das Weiterleitungsziel des "Benutzerdefinierter Status 2" und schaltet den Status der anrufenden Nebenstelle auf diesen um.
Das Ziel wird mittels DTMF übergeben. Bei Tischtelefonen kann dies wie unten beschrieben auf BLF Tasten gelegt werden.
Bei Apps und DECT Telefonen kann die Zielrufnummer nur über die Telefontastatur eingegeben werden.
Nutzungsbeispiel: *72 anrufen, "0172123456#" eingeben um eine Weiterleitung auf die Nummer 0172123456 einzurichten
### 73_dynWeiterleitung_aus ###
Das Skript *73 schaltet die Nebenstelle auf den Status "Verfügbar" um um die Weiterleitung zu deaktivieren.
Nutzungsbeispiel: *73 anrufen um die aktuelle Nebenstelle in den Status Verfügbar umzuschalten (die Weiterleitung zu deaktivieren)
### 721_dynWeiterleitung_mitExtension_an ###
Das Skript *721 arbeitet wie 72_dynWeiterleitung_an, allerdings wird zuerst die zu ändernde Nebenstelle mittels DTMF abgefragt.
Anschließend wird mittels DTFM die Zielrufnummer der Weiterleitung abgefragt, diese in das Weiterleitungsziel des "Benutzerdefinierter Status 2" eingetragen
und der Status der angegebenen Nebenstelle auf diesen umgeschaltet.
Die Nebenstelle und das Ziel wird mittels DTMF übergeben. Bei Tischtelefonen kann dies wie unten beschrieben auf BLF Tasten gelegt werden.
Bei Apps und DECT Telefonen kann die Zielrufnummer nur über die Telefontastatur eingegeben werden.
Nutzungsbeispiel: *721 anrufen, "80#83#" eingeben um für die Nebenstelle 80 eine Weiterleitung auf die Nummer IVR 83 einzurichten
Dies kann z.B. verwendet werden um die Dummy Nebenstelle 80 zwischen dem Anrufbeantworter "81 TAG", dem Anrufbeantworter "82 Nacht" oder "83 Urlaub" umzuschalten.
## Funktion auf BLF Tasten von Tischtelefonen legen ##
Die CFDs können ohne weitere Interaktion auf BLFs von Tischtelefonen provisioniert werden. Der Syntax ist vom Hersteller abhängig.
- Yealink BLF Tasten als indiv. Kurzwahl definieren :
z.B. Wtlg Handy: *72,0172123456#<br>
z.B. Wtlg Nst10: *72,10#<br>
z.B. Wtlg aus: *73<br>
- Fanvil BLF Tasten als indiv. Kurzwahl definieren :<br>
z.B. Wtlg Handy: *72,0172123456#<br>
z.B. Wtlg Nst10: *72,10#<br>
z.B. Wtlg aus: *73<br>
- Snom (ungetestet) BLF Tasten als indiv. Kurzwahl definieren:<br>
z.B. Wtlg Handy: *72;dtmf=0172123456#<br>
z.B. Wtlg Nst10: *72;dtmf=10#<br>
z.B. Wtlg aus: *73<br>
*Quellen / Nützliche Tools*
- [Ausgangspunkt war der Schnippsel von fxbastler aus dem 3CX Forum](https://www.3cx.de/forum/threads/rufweiterleitung.101354/page-2#post-430429)
- [Text-To-Speech German Vicky](https://ttsmp3.com/text-to-speech/German/)
- [g711.org mp3 zu wav konvertieren](https://g711.org/)
- [3CX Call Control API Doku](https://downloads-global.3cx.com/downloads/misc/callcontrolapi/3CXCallControlAPI_v20.zip)

BIN
RELEASE/VMNurAnsage.zip Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.