diff --git a/39_Profilstatus_mitExtension/39_Profilstatus_mitExtension.cfdproj b/39_Profilstatus_mitExtension/39_Profilstatus_mitExtension.cfdproj
new file mode 100644
index 0000000..882b74c
--- /dev/null
+++ b/39_Profilstatus_mitExtension/39_Profilstatus_mitExtension.cfdproj
@@ -0,0 +1,49 @@
+
+
+ 2.1
+
+
+
+
+
+
+ ExtensionNr
+ false
+ true
+
+
+
+ AuswahlOpt
+ false
+ true
+
+
+
+ Zielstatus
+ false
+ true
+
+
+
+
+ False
+ True
+ 0
+ 51
+ True
+ False
+ *39
+ None
+ None
+
+
+
+
+ us-east-2
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/39_Profilstatus_mitExtension/Audio/DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav b/39_Profilstatus_mitExtension/Audio/DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav
new file mode 100644
index 0000000..764f645
Binary files /dev/null and b/39_Profilstatus_mitExtension/Audio/DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav differ
diff --git a/39_Profilstatus_mitExtension/Audio/FehlerBeiDerEingabe-vicky.wav b/39_Profilstatus_mitExtension/Audio/FehlerBeiDerEingabe-vicky.wav
new file mode 100644
index 0000000..3a5504a
Binary files /dev/null and b/39_Profilstatus_mitExtension/Audio/FehlerBeiDerEingabe-vicky.wav differ
diff --git a/39_Profilstatus_mitExtension/Audio/Status0.wav b/39_Profilstatus_mitExtension/Audio/Status0.wav
new file mode 100644
index 0000000..b015dfe
Binary files /dev/null and b/39_Profilstatus_mitExtension/Audio/Status0.wav differ
diff --git a/39_Profilstatus_mitExtension/Audio/Status1.wav b/39_Profilstatus_mitExtension/Audio/Status1.wav
new file mode 100644
index 0000000..3ed67fd
Binary files /dev/null and b/39_Profilstatus_mitExtension/Audio/Status1.wav differ
diff --git a/39_Profilstatus_mitExtension/Audio/Status3.wav b/39_Profilstatus_mitExtension/Audio/Status3.wav
new file mode 100644
index 0000000..d403d3f
Binary files /dev/null and b/39_Profilstatus_mitExtension/Audio/Status3.wav differ
diff --git a/39_Profilstatus_mitExtension/Audio/Status4.wav b/39_Profilstatus_mitExtension/Audio/Status4.wav
new file mode 100644
index 0000000..e4c34c3
Binary files /dev/null and b/39_Profilstatus_mitExtension/Audio/Status4.wav differ
diff --git a/39_Profilstatus_mitExtension/Audio/Sttatus2.wav b/39_Profilstatus_mitExtension/Audio/Sttatus2.wav
new file mode 100644
index 0000000..79694b3
Binary files /dev/null and b/39_Profilstatus_mitExtension/Audio/Sttatus2.wav differ
diff --git a/39_Profilstatus_mitExtension/Audio/leer50ms.wav b/39_Profilstatus_mitExtension/Audio/leer50ms.wav
new file mode 100644
index 0000000..eea9512
Binary files /dev/null and b/39_Profilstatus_mitExtension/Audio/leer50ms.wav differ
diff --git a/39_Profilstatus_mitExtension/Audio/ttsMP3.com_VoiceText_2025-3-29_18-47-43_G711.org_.wav b/39_Profilstatus_mitExtension/Audio/ttsMP3.com_VoiceText_2025-3-29_18-47-43_G711.org_.wav
new file mode 100644
index 0000000..041adce
Binary files /dev/null and b/39_Profilstatus_mitExtension/Audio/ttsMP3.com_VoiceText_2025-3-29_18-47-43_G711.org_.wav differ
diff --git a/39_Profilstatus_mitExtension/Audio/welcheNSt.wav b/39_Profilstatus_mitExtension/Audio/welcheNSt.wav
new file mode 100644
index 0000000..6445dac
Binary files /dev/null and b/39_Profilstatus_mitExtension/Audio/welcheNSt.wav differ
diff --git a/39_Profilstatus_mitExtension/Audio/welcher_Status.wav b/39_Profilstatus_mitExtension/Audio/welcher_Status.wav
new file mode 100644
index 0000000..5e03130
Binary files /dev/null and b/39_Profilstatus_mitExtension/Audio/welcher_Status.wav differ
diff --git a/39_Profilstatus_mitExtension/Main.flow b/39_Profilstatus_mitExtension/Main.flow
new file mode 100644
index 0000000..b298008
--- /dev/null
+++ b/39_Profilstatus_mitExtension/Main.flow
@@ -0,0 +1,51 @@
+
+
+ 2.1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/39_Profilstatus_mitExtension/Output/Release/Audio/DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav b/39_Profilstatus_mitExtension/Output/Release/Audio/DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav
new file mode 100644
index 0000000..764f645
Binary files /dev/null and b/39_Profilstatus_mitExtension/Output/Release/Audio/DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav differ
diff --git a/39_Profilstatus_mitExtension/Output/Release/Audio/FehlerBeiDerEingabe-vicky.wav b/39_Profilstatus_mitExtension/Output/Release/Audio/FehlerBeiDerEingabe-vicky.wav
new file mode 100644
index 0000000..3a5504a
Binary files /dev/null and b/39_Profilstatus_mitExtension/Output/Release/Audio/FehlerBeiDerEingabe-vicky.wav differ
diff --git a/39_Profilstatus_mitExtension/Output/Release/Audio/Status0.wav b/39_Profilstatus_mitExtension/Output/Release/Audio/Status0.wav
new file mode 100644
index 0000000..b015dfe
Binary files /dev/null and b/39_Profilstatus_mitExtension/Output/Release/Audio/Status0.wav differ
diff --git a/39_Profilstatus_mitExtension/Output/Release/Audio/Status1.wav b/39_Profilstatus_mitExtension/Output/Release/Audio/Status1.wav
new file mode 100644
index 0000000..3ed67fd
Binary files /dev/null and b/39_Profilstatus_mitExtension/Output/Release/Audio/Status1.wav differ
diff --git a/39_Profilstatus_mitExtension/Output/Release/Audio/Status3.wav b/39_Profilstatus_mitExtension/Output/Release/Audio/Status3.wav
new file mode 100644
index 0000000..d403d3f
Binary files /dev/null and b/39_Profilstatus_mitExtension/Output/Release/Audio/Status3.wav differ
diff --git a/39_Profilstatus_mitExtension/Output/Release/Audio/Status4.wav b/39_Profilstatus_mitExtension/Output/Release/Audio/Status4.wav
new file mode 100644
index 0000000..e4c34c3
Binary files /dev/null and b/39_Profilstatus_mitExtension/Output/Release/Audio/Status4.wav differ
diff --git a/39_Profilstatus_mitExtension/Output/Release/Audio/Sttatus2.wav b/39_Profilstatus_mitExtension/Output/Release/Audio/Sttatus2.wav
new file mode 100644
index 0000000..79694b3
Binary files /dev/null and b/39_Profilstatus_mitExtension/Output/Release/Audio/Sttatus2.wav differ
diff --git a/39_Profilstatus_mitExtension/Output/Release/Audio/leer50ms.wav b/39_Profilstatus_mitExtension/Output/Release/Audio/leer50ms.wav
new file mode 100644
index 0000000..eea9512
Binary files /dev/null and b/39_Profilstatus_mitExtension/Output/Release/Audio/leer50ms.wav differ
diff --git a/39_Profilstatus_mitExtension/Output/Release/Audio/welcheNSt.wav b/39_Profilstatus_mitExtension/Output/Release/Audio/welcheNSt.wav
new file mode 100644
index 0000000..6445dac
Binary files /dev/null and b/39_Profilstatus_mitExtension/Output/Release/Audio/welcheNSt.wav differ
diff --git a/39_Profilstatus_mitExtension/Output/Release/Audio/welcher_Status.wav b/39_Profilstatus_mitExtension/Output/Release/Audio/welcher_Status.wav
new file mode 100644
index 0000000..5e03130
Binary files /dev/null and b/39_Profilstatus_mitExtension/Output/Release/Audio/welcher_Status.wav differ
diff --git a/39_Profilstatus_mitExtension/Output/Release/Script/Main.cs b/39_Profilstatus_mitExtension/Output/Release/Script/Main.cs
new file mode 100644
index 0000000..edab99a
--- /dev/null
+++ b/39_Profilstatus_mitExtension/Output/Release/Script/Main.cs
@@ -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, ICallflow, ICallflowProcessor
+ {
+ private bool executionStarted;
+ private bool executionFinished;
+ private bool disconnectFlowPending;
+
+ private BufferBlock eventBuffer;
+
+ private int currentComponentIndex;
+ private List mainFlowComponentList;
+ private List disconnectFlowComponentList;
+ private List errorFlowComponentList;
+ private List currentFlowComponentList;
+
+ private LogFormatter logFormatter;
+ private TimerManager timerManager;
+ private Dictionary 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("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("InputExtension_Conditional");
+ mainFlowComponentList.Add(InputExtension_Conditional);
+ InputExtension_Conditional.ConditionList.Add(() => { return InputExtension.Result == UserInputComponent.UserInputResults.ValidDigits; });
+ InputExtension_Conditional.ContainerList.Add(scope.CreateComponent("InputExtension_Conditional_ValidInput"));
+ InputExtension_Conditional.ConditionList.Add(() => { return InputExtension.Result == UserInputComponent.UserInputResults.InvalidDigits || InputExtension.Result == UserInputComponent.UserInputResults.Timeout; });
+ InputExtension_Conditional.ContainerList.Add(scope.CreateComponent("InputExtension_Conditional_InvalidInput"));
+ VariableAssignmentComponent variableAssignmentExtensionNr = scope.CreateComponent("variableAssignmentExtensionNr");
+ variableAssignmentExtensionNr.VariableName = "project$.ExtensionNr";
+ variableAssignmentExtensionNr.VariableValueHandler = () => { return InputExtension.Buffer; };
+ mainFlowComponentList.Add(variableAssignmentExtensionNr);
+ UserInputComponent InputStatus = scope.CreateComponent("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("InputStatus_Conditional");
+ mainFlowComponentList.Add(InputStatus_Conditional);
+ InputStatus_Conditional.ConditionList.Add(() => { return InputStatus.Result == UserInputComponent.UserInputResults.ValidDigits; });
+ InputStatus_Conditional.ContainerList.Add(scope.CreateComponent("InputStatus_Conditional_ValidInput"));
+ InputStatus_Conditional.ConditionList.Add(() => { return InputStatus.Result == UserInputComponent.UserInputResults.InvalidDigits || InputStatus.Result == UserInputComponent.UserInputResults.Timeout; });
+ InputStatus_Conditional.ContainerList.Add(scope.CreateComponent("InputStatus_Conditional_InvalidInput"));
+ VariableAssignmentComponent variableAssignmentZielStatus = scope.CreateComponent("variableAssignmentZielStatus");
+ variableAssignmentZielStatus.VariableName = "project$.Zielstatus";
+ variableAssignmentZielStatus.VariableValueHandler = () => { return InputStatus.Buffer; };
+ mainFlowComponentList.Add(variableAssignmentZielStatus);
+ ConditionalComponent CreateCondition1 = scope.CreateComponent("CreateCondition1");
+ mainFlowComponentList.Add(CreateCondition1);
+ CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["project$.Zielstatus"].Value,0)); });
+ CreateCondition1.ContainerList.Add(scope.CreateComponent("conditionalComponentBranch1"));
+ TcxSetExtensionStatusComponent SetExtensionStatus_Available = scope.CreateComponent("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("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("conditionalComponentBranch2"));
+ TcxSetExtensionStatusComponent SetExtensionStatus_Away = scope.CreateComponent("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("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("conditionalComponentBranch3"));
+ TcxSetExtensionStatusComponent SetExtensionStatus_DND = scope.CreateComponent("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("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("conditionalComponentBranch4"));
+ TcxSetExtensionStatusComponent SetExtensionStatus_Custom1 = scope.CreateComponent("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("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("conditionalComponentBranch5"));
+ TcxSetExtensionStatusComponent SetExtensionStatus_Custom2 = scope.CreateComponent("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("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("mainAutoAddedFinalDisconnectCall");
+ DisconnectCallComponent errorHandlerAutoAddedFinalDisconnectCall = scope.CreateComponent("errorHandlerAutoAddedFinalDisconnectCall");
+ mainFlowComponentList.Add(mainAutoAddedFinalDisconnectCall);
+ errorFlowComponentList.Add(errorHandlerAutoAddedFinalDisconnectCall);
+ }
+
+ public Main()
+ {
+ this.executionStarted = false;
+ this.executionFinished = false;
+ this.disconnectFlowPending = false;
+
+ this.eventBuffer = new BufferBlock();
+
+ this.currentComponentIndex = 0;
+ this.mainFlowComponentList = new List();
+ this.disconnectFlowComponentList = new List();
+ this.errorFlowComponentList = new List();
+ this.currentFlowComponentList = mainFlowComponentList;
+
+ this.timerManager = new TimerManager();
+ this.timerManager.OnTimeout += (state) => eventBuffer.Post(new TimeoutEvent(state));
+ this.variableMap = new Dictionary();
+
+ 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();
+ }
+ }
+
+
+
+ }
+}
diff --git a/39_Profilstatus_mitExtension/Output/Release/_39_Profilstatus_mitExtension.zip b/39_Profilstatus_mitExtension/Output/Release/_39_Profilstatus_mitExtension.zip
new file mode 100644
index 0000000..cf06f14
Binary files /dev/null and b/39_Profilstatus_mitExtension/Output/Release/_39_Profilstatus_mitExtension.zip differ
diff --git a/39_Profilstatus_mitExtension/README.md b/39_Profilstatus_mitExtension/README.md
new file mode 100644
index 0000000..728ea7b
--- /dev/null
+++ b/39_Profilstatus_mitExtension/README.md
@@ -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#
+ z.B. Wtlg Nst10: *72,10#
+ z.B. Wtlg aus: *73
+- Fanvil BLF Tasten als indiv. Kurzwahl definieren :
+ z.B. Wtlg Handy: *72,0172123456#
+ z.B. Wtlg Nst10: *72,10#
+ z.B. Wtlg aus: *73
+- Snom (ungetestet) BLF Tasten als indiv. Kurzwahl definieren:
+ z.B. Wtlg Handy: *72;dtmf=0172123456#
+ z.B. Wtlg Nst10: *72;dtmf=10#
+ z.B. Wtlg aus: *73
+
+
diff --git a/721_dynWeiterleitung_mitExtension_an/721_dynWeiterleitung_mitExtension_an.cfdproj b/721_dynWeiterleitung_mitExtension_an/721_dynWeiterleitung_mitExtension_an.cfdproj
new file mode 100644
index 0000000..28c5a77
--- /dev/null
+++ b/721_dynWeiterleitung_mitExtension_an/721_dynWeiterleitung_mitExtension_an.cfdproj
@@ -0,0 +1,50 @@
+
+
+ 2.1
+
+
+
+
+
+
+
+ ExtensionNr
+ false
+ true
+
+
+
+ AuswahlOpt
+ false
+ true
+
+
+
+ ZielNr
+ false
+ true
+
+
+
+
+ False
+ True
+ 0
+ 56
+ True
+ False
+ *721
+ None
+ None
+
+
+
+
+ us-east-2
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/721_dynWeiterleitung_mitExtension_an/Audio/Bitte geben Sie die Zielnummer der Weiterleitung ein oder.wav b/721_dynWeiterleitung_mitExtension_an/Audio/Bitte geben Sie die Zielnummer der Weiterleitung ein oder.wav
new file mode 100644
index 0000000..3d55d2b
Binary files /dev/null and b/721_dynWeiterleitung_mitExtension_an/Audio/Bitte geben Sie die Zielnummer der Weiterleitung ein oder.wav differ
diff --git a/721_dynWeiterleitung_mitExtension_an/Audio/BitteGebenSieDieNummerDerWeiterleitungEin-vicki.wav b/721_dynWeiterleitung_mitExtension_an/Audio/BitteGebenSieDieNummerDerWeiterleitungEin-vicki.wav
new file mode 100644
index 0000000..3539093
Binary files /dev/null and b/721_dynWeiterleitung_mitExtension_an/Audio/BitteGebenSieDieNummerDerWeiterleitungEin-vicki.wav differ
diff --git a/721_dynWeiterleitung_mitExtension_an/Audio/DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav b/721_dynWeiterleitung_mitExtension_an/Audio/DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav
new file mode 100644
index 0000000..764f645
Binary files /dev/null and b/721_dynWeiterleitung_mitExtension_an/Audio/DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav differ
diff --git a/721_dynWeiterleitung_mitExtension_an/Audio/FehlerBeiDerEingabe-vicky.wav b/721_dynWeiterleitung_mitExtension_an/Audio/FehlerBeiDerEingabe-vicky.wav
new file mode 100644
index 0000000..3a5504a
Binary files /dev/null and b/721_dynWeiterleitung_mitExtension_an/Audio/FehlerBeiDerEingabe-vicky.wav differ
diff --git a/721_dynWeiterleitung_mitExtension_an/Audio/IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav b/721_dynWeiterleitung_mitExtension_an/Audio/IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav
new file mode 100644
index 0000000..2e2b922
Binary files /dev/null and b/721_dynWeiterleitung_mitExtension_an/Audio/IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav differ
diff --git a/721_dynWeiterleitung_mitExtension_an/Audio/WecheNebenstelleSollWeitergeitetwerden.wav b/721_dynWeiterleitung_mitExtension_an/Audio/WecheNebenstelleSollWeitergeitetwerden.wav
new file mode 100644
index 0000000..f2e9dc2
Binary files /dev/null and b/721_dynWeiterleitung_mitExtension_an/Audio/WecheNebenstelleSollWeitergeitetwerden.wav differ
diff --git a/721_dynWeiterleitung_mitExtension_an/Audio/Weiterleitung wurde entfernt.wav b/721_dynWeiterleitung_mitExtension_an/Audio/Weiterleitung wurde entfernt.wav
new file mode 100644
index 0000000..afd9b8f
Binary files /dev/null and b/721_dynWeiterleitung_mitExtension_an/Audio/Weiterleitung wurde entfernt.wav differ
diff --git a/721_dynWeiterleitung_mitExtension_an/Audio/leer50ms.wav b/721_dynWeiterleitung_mitExtension_an/Audio/leer50ms.wav
new file mode 100644
index 0000000..eea9512
Binary files /dev/null and b/721_dynWeiterleitung_mitExtension_an/Audio/leer50ms.wav differ
diff --git a/721_dynWeiterleitung_mitExtension_an/Main.flow b/721_dynWeiterleitung_mitExtension_an/Main.flow
new file mode 100644
index 0000000..ae8284d
--- /dev/null
+++ b/721_dynWeiterleitung_mitExtension_an/Main.flow
@@ -0,0 +1,31 @@
+
+
+ 2.1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/721_dynWeiterleitung_mitExtension_an/Output/Release/Audio/Bitte geben Sie die Zielnummer der Weiterleitung ein oder.wav b/721_dynWeiterleitung_mitExtension_an/Output/Release/Audio/Bitte geben Sie die Zielnummer der Weiterleitung ein oder.wav
new file mode 100644
index 0000000..3d55d2b
Binary files /dev/null and b/721_dynWeiterleitung_mitExtension_an/Output/Release/Audio/Bitte geben Sie die Zielnummer der Weiterleitung ein oder.wav differ
diff --git a/721_dynWeiterleitung_mitExtension_an/Output/Release/Audio/DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav b/721_dynWeiterleitung_mitExtension_an/Output/Release/Audio/DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav
new file mode 100644
index 0000000..764f645
Binary files /dev/null and b/721_dynWeiterleitung_mitExtension_an/Output/Release/Audio/DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav differ
diff --git a/721_dynWeiterleitung_mitExtension_an/Output/Release/Audio/FehlerBeiDerEingabe-vicky.wav b/721_dynWeiterleitung_mitExtension_an/Output/Release/Audio/FehlerBeiDerEingabe-vicky.wav
new file mode 100644
index 0000000..3a5504a
Binary files /dev/null and b/721_dynWeiterleitung_mitExtension_an/Output/Release/Audio/FehlerBeiDerEingabe-vicky.wav differ
diff --git a/721_dynWeiterleitung_mitExtension_an/Output/Release/Audio/IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav b/721_dynWeiterleitung_mitExtension_an/Output/Release/Audio/IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav
new file mode 100644
index 0000000..2e2b922
Binary files /dev/null and b/721_dynWeiterleitung_mitExtension_an/Output/Release/Audio/IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav differ
diff --git a/721_dynWeiterleitung_mitExtension_an/Output/Release/Audio/WecheNebenstelleSollWeitergeitetwerden.wav b/721_dynWeiterleitung_mitExtension_an/Output/Release/Audio/WecheNebenstelleSollWeitergeitetwerden.wav
new file mode 100644
index 0000000..f2e9dc2
Binary files /dev/null and b/721_dynWeiterleitung_mitExtension_an/Output/Release/Audio/WecheNebenstelleSollWeitergeitetwerden.wav differ
diff --git a/721_dynWeiterleitung_mitExtension_an/Output/Release/Audio/Weiterleitung wurde entfernt.wav b/721_dynWeiterleitung_mitExtension_an/Output/Release/Audio/Weiterleitung wurde entfernt.wav
new file mode 100644
index 0000000..afd9b8f
Binary files /dev/null and b/721_dynWeiterleitung_mitExtension_an/Output/Release/Audio/Weiterleitung wurde entfernt.wav differ
diff --git a/721_dynWeiterleitung_mitExtension_an/Output/Release/Audio/leer50ms.wav b/721_dynWeiterleitung_mitExtension_an/Output/Release/Audio/leer50ms.wav
new file mode 100644
index 0000000..eea9512
Binary files /dev/null and b/721_dynWeiterleitung_mitExtension_an/Output/Release/Audio/leer50ms.wav differ
diff --git a/721_dynWeiterleitung_mitExtension_an/Output/Release/Script/Main.cs b/721_dynWeiterleitung_mitExtension_an/Output/Release/Script/Main.cs
new file mode 100644
index 0000000..5039570
--- /dev/null
+++ b/721_dynWeiterleitung_mitExtension_an/Output/Release/Script/Main.cs
@@ -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, ICallflow, ICallflowProcessor
+ {
+ private bool executionStarted;
+ private bool executionFinished;
+ private bool disconnectFlowPending;
+
+ private BufferBlock eventBuffer;
+
+ private int currentComponentIndex;
+ private List mainFlowComponentList;
+ private List disconnectFlowComponentList;
+ private List errorFlowComponentList;
+ private List currentFlowComponentList;
+
+ private LogFormatter logFormatter;
+ private TimerManager timerManager;
+ private Dictionary 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("Logger1");
+ Logger1.Level = LoggerComponent.LogLevels.Info;
+ Logger1.TextHandler = () => { return Convert.ToString("huhu"); };
+ mainFlowComponentList.Add(Logger1);
+ UserInputComponent InputExtension = scope.CreateComponent("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("InputExtension_Conditional");
+ mainFlowComponentList.Add(InputExtension_Conditional);
+ InputExtension_Conditional.ConditionList.Add(() => { return InputExtension.Result == UserInputComponent.UserInputResults.ValidDigits; });
+ InputExtension_Conditional.ContainerList.Add(scope.CreateComponent("InputExtension_Conditional_ValidInput"));
+ InputExtension_Conditional.ConditionList.Add(() => { return InputExtension.Result == UserInputComponent.UserInputResults.InvalidDigits || InputExtension.Result == UserInputComponent.UserInputResults.Timeout; });
+ InputExtension_Conditional.ContainerList.Add(scope.CreateComponent("InputExtension_Conditional_InvalidInput"));
+ UserInputComponent InputDestination = scope.CreateComponent("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("InputDestination_Conditional");
+ mainFlowComponentList.Add(InputDestination_Conditional);
+ InputDestination_Conditional.ConditionList.Add(() => { return InputDestination.Result == UserInputComponent.UserInputResults.ValidDigits; });
+ InputDestination_Conditional.ContainerList.Add(scope.CreateComponent("InputDestination_Conditional_ValidInput"));
+ InputDestination_Conditional.ConditionList.Add(() => { return InputDestination.Result == UserInputComponent.UserInputResults.InvalidDigits || InputDestination.Result == UserInputComponent.UserInputResults.Timeout; });
+ InputDestination_Conditional.ContainerList.Add(scope.CreateComponent("InputDestination_Conditional_InvalidInput"));
+ VariableAssignmentComponent variableAssignmentExtensionNr = scope.CreateComponent("variableAssignmentExtensionNr");
+ variableAssignmentExtensionNr.VariableName = "project$.ExtensionNr";
+ variableAssignmentExtensionNr.VariableValueHandler = () => { return InputExtension.Buffer; };
+ mainFlowComponentList.Add(variableAssignmentExtensionNr);
+ VariableAssignmentComponent variableAssignmentZielNr = scope.CreateComponent("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("mainAutoAddedFinalDisconnectCall");
+ DisconnectCallComponent errorHandlerAutoAddedFinalDisconnectCall = scope.CreateComponent("errorHandlerAutoAddedFinalDisconnectCall");
+ mainFlowComponentList.Add(mainAutoAddedFinalDisconnectCall);
+ errorFlowComponentList.Add(errorHandlerAutoAddedFinalDisconnectCall);
+ }
+
+ public Main()
+ {
+ this.executionStarted = false;
+ this.executionFinished = false;
+ this.disconnectFlowPending = false;
+
+ this.eventBuffer = new BufferBlock();
+
+ this.currentComponentIndex = 0;
+ this.mainFlowComponentList = new List();
+ this.disconnectFlowComponentList = new List();
+ this.errorFlowComponentList = new List();
+ this.currentFlowComponentList = mainFlowComponentList;
+
+ this.timerManager = new TimerManager();
+ this.timerManager.OnTimeout += (state) => eventBuffer.Post(new TimeoutEvent(state));
+ this.variableMap = new Dictionary();
+
+ 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 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("CreateCondition1");
+ mainFlowComponentList.Add(CreateCondition1);
+ CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(SetExtDNDDest.ReturnValue,0)); });
+ CreateCondition1.ContainerList.Add(scope.CreateComponent("conditionalComponentBranch1"));
+ TcxSetExtensionStatusComponent SetAvailable = scope.CreateComponent("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("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("conditionalComponentBranch2"));
+ TcxSetExtensionStatusComponent SetExtCustom2 = scope.CreateComponent("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("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 result = officeHours.IsActiveTime(nowDt);
+ return result.GetValueOrDefault(false);
+ }
+ }
+public class SetExtDNDDest514741345ECCComponent : ExternalCodeExecutionComponent
+ {
+ public List Parameters { get; } = new List();
+ 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); }
+ }
+
+ }
+}
diff --git a/721_dynWeiterleitung_mitExtension_an/Output/Release/_721_dynWeiterleitung_mitExtension_an.zip b/721_dynWeiterleitung_mitExtension_an/Output/Release/_721_dynWeiterleitung_mitExtension_an.zip
new file mode 100644
index 0000000..5175d96
Binary files /dev/null and b/721_dynWeiterleitung_mitExtension_an/Output/Release/_721_dynWeiterleitung_mitExtension_an.zip differ
diff --git a/721_dynWeiterleitung_mitExtension_an/README.md b/721_dynWeiterleitung_mitExtension_an/README.md
new file mode 100644
index 0000000..a46d49a
--- /dev/null
+++ b/721_dynWeiterleitung_mitExtension_an/README.md
@@ -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#
+ z.B. Wtlg Nst10: *72,10#
+ z.B. Wtlg aus: *73
+- Fanvil BLF Tasten als indiv. Kurzwahl definieren :
+ z.B. Wtlg Handy: *72,0172123456#
+ z.B. Wtlg Nst10: *72,10#
+ z.B. Wtlg aus: *73
+- Snom (ungetestet) BLF Tasten als indiv. Kurzwahl definieren:
+ z.B. Wtlg Handy: *72;dtmf=0172123456#
+ z.B. Wtlg Nst10: *72;dtmf=10#
+ z.B. Wtlg aus: *73
+
+
+*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)
diff --git a/721_dynWeiterleitung_mitExtension_an/setStateAndFWDestination.comp b/721_dynWeiterleitung_mitExtension_an/setStateAndFWDestination.comp
new file mode 100644
index 0000000..b401a67
--- /dev/null
+++ b/721_dynWeiterleitung_mitExtension_an/setStateAndFWDestination.comp
@@ -0,0 +1,43 @@
+
+
+ 2.1
+
+
+
+ strDestNo
+ true
+ true
+
+
+
+ strExtensionNo
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/72_dynWeiterleitung_an/72_dynWeiterleitung_an.cfdproj b/72_dynWeiterleitung_an/72_dynWeiterleitung_an.cfdproj
new file mode 100644
index 0000000..d98c58a
--- /dev/null
+++ b/72_dynWeiterleitung_an/72_dynWeiterleitung_an.cfdproj
@@ -0,0 +1,50 @@
+
+
+ 2.1
+
+
+
+
+
+
+
+ ExtensionNr
+ false
+ true
+
+
+
+ AuswahlOpt
+ false
+ true
+
+
+
+ ZielNr
+ false
+ true
+
+
+
+
+ False
+ True
+ 0
+ 49
+ True
+ False
+ *72
+ None
+ None
+
+
+
+
+ us-east-2
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/72_dynWeiterleitung_an/Audio/Bitte geben Sie die Zielnummer der Weiterleitung ein oder.wav b/72_dynWeiterleitung_an/Audio/Bitte geben Sie die Zielnummer der Weiterleitung ein oder.wav
new file mode 100644
index 0000000..3d55d2b
Binary files /dev/null and b/72_dynWeiterleitung_an/Audio/Bitte geben Sie die Zielnummer der Weiterleitung ein oder.wav differ
diff --git a/72_dynWeiterleitung_an/Audio/BitteGebenSieDieNummerDerWeiterleitungEin-vicki.wav b/72_dynWeiterleitung_an/Audio/BitteGebenSieDieNummerDerWeiterleitungEin-vicki.wav
new file mode 100644
index 0000000..3539093
Binary files /dev/null and b/72_dynWeiterleitung_an/Audio/BitteGebenSieDieNummerDerWeiterleitungEin-vicki.wav differ
diff --git a/72_dynWeiterleitung_an/Audio/DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav b/72_dynWeiterleitung_an/Audio/DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav
new file mode 100644
index 0000000..764f645
Binary files /dev/null and b/72_dynWeiterleitung_an/Audio/DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav differ
diff --git a/72_dynWeiterleitung_an/Audio/FehlerBeiDerEingabe-vicky.wav b/72_dynWeiterleitung_an/Audio/FehlerBeiDerEingabe-vicky.wav
new file mode 100644
index 0000000..3a5504a
Binary files /dev/null and b/72_dynWeiterleitung_an/Audio/FehlerBeiDerEingabe-vicky.wav differ
diff --git a/72_dynWeiterleitung_an/Audio/IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav b/72_dynWeiterleitung_an/Audio/IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav
new file mode 100644
index 0000000..2e2b922
Binary files /dev/null and b/72_dynWeiterleitung_an/Audio/IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav differ
diff --git a/72_dynWeiterleitung_an/Audio/Weiterleitung wurde entfernt.wav b/72_dynWeiterleitung_an/Audio/Weiterleitung wurde entfernt.wav
new file mode 100644
index 0000000..afd9b8f
Binary files /dev/null and b/72_dynWeiterleitung_an/Audio/Weiterleitung wurde entfernt.wav differ
diff --git a/72_dynWeiterleitung_an/Audio/leer50ms.wav b/72_dynWeiterleitung_an/Audio/leer50ms.wav
new file mode 100644
index 0000000..eea9512
Binary files /dev/null and b/72_dynWeiterleitung_an/Audio/leer50ms.wav differ
diff --git a/72_dynWeiterleitung_an/Main.flow b/72_dynWeiterleitung_an/Main.flow
new file mode 100644
index 0000000..c509b70
--- /dev/null
+++ b/72_dynWeiterleitung_an/Main.flow
@@ -0,0 +1,26 @@
+
+
+ 2.1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/72_dynWeiterleitung_an/Output/Release/Audio/Bitte geben Sie die Zielnummer der Weiterleitung ein oder.wav b/72_dynWeiterleitung_an/Output/Release/Audio/Bitte geben Sie die Zielnummer der Weiterleitung ein oder.wav
new file mode 100644
index 0000000..3d55d2b
Binary files /dev/null and b/72_dynWeiterleitung_an/Output/Release/Audio/Bitte geben Sie die Zielnummer der Weiterleitung ein oder.wav differ
diff --git a/72_dynWeiterleitung_an/Output/Release/Audio/DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav b/72_dynWeiterleitung_an/Output/Release/Audio/DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav
new file mode 100644
index 0000000..764f645
Binary files /dev/null and b/72_dynWeiterleitung_an/Output/Release/Audio/DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav differ
diff --git a/72_dynWeiterleitung_an/Output/Release/Audio/FehlerBeiDerEingabe-vicky.wav b/72_dynWeiterleitung_an/Output/Release/Audio/FehlerBeiDerEingabe-vicky.wav
new file mode 100644
index 0000000..3a5504a
Binary files /dev/null and b/72_dynWeiterleitung_an/Output/Release/Audio/FehlerBeiDerEingabe-vicky.wav differ
diff --git a/72_dynWeiterleitung_an/Output/Release/Audio/IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav b/72_dynWeiterleitung_an/Output/Release/Audio/IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav
new file mode 100644
index 0000000..2e2b922
Binary files /dev/null and b/72_dynWeiterleitung_an/Output/Release/Audio/IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav differ
diff --git a/72_dynWeiterleitung_an/Output/Release/Audio/Weiterleitung wurde entfernt.wav b/72_dynWeiterleitung_an/Output/Release/Audio/Weiterleitung wurde entfernt.wav
new file mode 100644
index 0000000..afd9b8f
Binary files /dev/null and b/72_dynWeiterleitung_an/Output/Release/Audio/Weiterleitung wurde entfernt.wav differ
diff --git a/72_dynWeiterleitung_an/Output/Release/Audio/leer50ms.wav b/72_dynWeiterleitung_an/Output/Release/Audio/leer50ms.wav
new file mode 100644
index 0000000..eea9512
Binary files /dev/null and b/72_dynWeiterleitung_an/Output/Release/Audio/leer50ms.wav differ
diff --git a/72_dynWeiterleitung_an/Output/Release/Script/Main.cs b/72_dynWeiterleitung_an/Output/Release/Script/Main.cs
new file mode 100644
index 0000000..a714f1d
--- /dev/null
+++ b/72_dynWeiterleitung_an/Output/Release/Script/Main.cs
@@ -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, ICallflow, ICallflowProcessor
+ {
+ private bool executionStarted;
+ private bool executionFinished;
+ private bool disconnectFlowPending;
+
+ private BufferBlock eventBuffer;
+
+ private int currentComponentIndex;
+ private List mainFlowComponentList;
+ private List disconnectFlowComponentList;
+ private List errorFlowComponentList;
+ private List currentFlowComponentList;
+
+ private LogFormatter logFormatter;
+ private TimerManager timerManager;
+ private Dictionary 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("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("InputDestination_Conditional");
+ mainFlowComponentList.Add(InputDestination_Conditional);
+ InputDestination_Conditional.ConditionList.Add(() => { return InputDestination.Result == UserInputComponent.UserInputResults.ValidDigits; });
+ InputDestination_Conditional.ContainerList.Add(scope.CreateComponent("InputDestination_Conditional_ValidInput"));
+ InputDestination_Conditional.ConditionList.Add(() => { return InputDestination.Result == UserInputComponent.UserInputResults.InvalidDigits || InputDestination.Result == UserInputComponent.UserInputResults.Timeout; });
+ InputDestination_Conditional.ContainerList.Add(scope.CreateComponent("InputDestination_Conditional_InvalidInput"));
+ VariableAssignmentComponent variableAssignmentExtensionNr = scope.CreateComponent("variableAssignmentExtensionNr");
+ variableAssignmentExtensionNr.VariableName = "project$.ExtensionNr";
+ variableAssignmentExtensionNr.VariableValueHandler = () => { return variableMap["session.ani"].Value; };
+ mainFlowComponentList.Add(variableAssignmentExtensionNr);
+ VariableAssignmentComponent variableAssignmentZielNr = scope.CreateComponent("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("mainAutoAddedFinalDisconnectCall");
+ DisconnectCallComponent errorHandlerAutoAddedFinalDisconnectCall = scope.CreateComponent("errorHandlerAutoAddedFinalDisconnectCall");
+ mainFlowComponentList.Add(mainAutoAddedFinalDisconnectCall);
+ errorFlowComponentList.Add(errorHandlerAutoAddedFinalDisconnectCall);
+ }
+
+ public Main()
+ {
+ this.executionStarted = false;
+ this.executionFinished = false;
+ this.disconnectFlowPending = false;
+
+ this.eventBuffer = new BufferBlock();
+
+ this.currentComponentIndex = 0;
+ this.mainFlowComponentList = new List();
+ this.disconnectFlowComponentList = new List();
+ this.errorFlowComponentList = new List();
+ this.currentFlowComponentList = mainFlowComponentList;
+
+ this.timerManager = new TimerManager();
+ this.timerManager.OnTimeout += (state) => eventBuffer.Post(new TimeoutEvent(state));
+ this.variableMap = new Dictionary();
+
+ 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 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("CreateCondition1");
+ mainFlowComponentList.Add(CreateCondition1);
+ CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(SetExtDNDDest.ReturnValue,0)); });
+ CreateCondition1.ContainerList.Add(scope.CreateComponent("conditionalComponentBranch1"));
+ TcxSetExtensionStatusComponent SetAvailable = scope.CreateComponent("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("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("conditionalComponentBranch2"));
+ TcxSetExtensionStatusComponent SetExtCustom2 = scope.CreateComponent("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("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 result = officeHours.IsActiveTime(nowDt);
+ return result.GetValueOrDefault(false);
+ }
+ }
+public class SetExtDNDDest1287229307ECCComponent : ExternalCodeExecutionComponent
+ {
+ public List Parameters { get; } = new List();
+ 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); }
+ }
+
+ }
+}
diff --git a/72_dynWeiterleitung_an/Output/Release/_72_dynWeiterleitung_an.zip b/72_dynWeiterleitung_an/Output/Release/_72_dynWeiterleitung_an.zip
new file mode 100644
index 0000000..867b4a4
Binary files /dev/null and b/72_dynWeiterleitung_an/Output/Release/_72_dynWeiterleitung_an.zip differ
diff --git a/72_dynWeiterleitung_an/README.md b/72_dynWeiterleitung_an/README.md
new file mode 100644
index 0000000..d6d810a
--- /dev/null
+++ b/72_dynWeiterleitung_an/README.md
@@ -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#
+ z.B. Wtlg Nst10: *72,10#
+ z.B. Wtlg aus: *73
+- Fanvil BLF Tasten als indiv. Kurzwahl definieren :
+ z.B. Wtlg Handy: *72,0172123456#
+ z.B. Wtlg Nst10: *72,10#
+ z.B. Wtlg aus: *73
+- Snom (ungetestet) BLF Tasten als indiv. Kurzwahl definieren:
+ z.B. Wtlg Handy: *72;dtmf=0172123456#
+ z.B. Wtlg Nst10: *72;dtmf=10#
+ z.B. Wtlg aus: *73
+
+
+*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)
diff --git a/72_dynWeiterleitung_an/setStateAndFWDestination.comp b/72_dynWeiterleitung_an/setStateAndFWDestination.comp
new file mode 100644
index 0000000..b401a67
--- /dev/null
+++ b/72_dynWeiterleitung_an/setStateAndFWDestination.comp
@@ -0,0 +1,43 @@
+
+
+ 2.1
+
+
+
+ strDestNo
+ true
+ true
+
+
+
+ strExtensionNo
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/73_dynWeiterleitung_aus/73_dynWeiterleitung_aus.cfdproj b/73_dynWeiterleitung_aus/73_dynWeiterleitung_aus.cfdproj
new file mode 100644
index 0000000..256f9e8
--- /dev/null
+++ b/73_dynWeiterleitung_aus/73_dynWeiterleitung_aus.cfdproj
@@ -0,0 +1,50 @@
+
+
+ 2.1
+
+
+
+
+
+
+
+ ExtensionNr
+ false
+ true
+
+
+
+ AuswahlOpt
+ false
+ true
+
+
+
+ ZielNr
+ false
+ true
+
+
+
+
+ False
+ True
+ 0
+ 48
+ True
+ False
+ *73
+ None
+ None
+
+
+
+
+ us-east-2
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/73_dynWeiterleitung_aus/Audio/Bitte geben Sie die Zielnummer der Weiterleitung ein oder.wav b/73_dynWeiterleitung_aus/Audio/Bitte geben Sie die Zielnummer der Weiterleitung ein oder.wav
new file mode 100644
index 0000000..3d55d2b
Binary files /dev/null and b/73_dynWeiterleitung_aus/Audio/Bitte geben Sie die Zielnummer der Weiterleitung ein oder.wav differ
diff --git a/73_dynWeiterleitung_aus/Audio/BitteGebenSieDieNummerDerWeiterleitungEin-vicki.wav b/73_dynWeiterleitung_aus/Audio/BitteGebenSieDieNummerDerWeiterleitungEin-vicki.wav
new file mode 100644
index 0000000..3539093
Binary files /dev/null and b/73_dynWeiterleitung_aus/Audio/BitteGebenSieDieNummerDerWeiterleitungEin-vicki.wav differ
diff --git a/73_dynWeiterleitung_aus/Audio/DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav b/73_dynWeiterleitung_aus/Audio/DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav
new file mode 100644
index 0000000..764f645
Binary files /dev/null and b/73_dynWeiterleitung_aus/Audio/DasHatZuLangeGedauertBitteNochEinmalProbieren-vicki.wav differ
diff --git a/73_dynWeiterleitung_aus/Audio/FehlerBeiDerEingabe-vicky.wav b/73_dynWeiterleitung_aus/Audio/FehlerBeiDerEingabe-vicky.wav
new file mode 100644
index 0000000..3a5504a
Binary files /dev/null and b/73_dynWeiterleitung_aus/Audio/FehlerBeiDerEingabe-vicky.wav differ
diff --git a/73_dynWeiterleitung_aus/Audio/IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav b/73_dynWeiterleitung_aus/Audio/IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav
new file mode 100644
index 0000000..2e2b922
Binary files /dev/null and b/73_dynWeiterleitung_aus/Audio/IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav differ
diff --git a/73_dynWeiterleitung_aus/Audio/Weiterleitung wurde entfernt.wav b/73_dynWeiterleitung_aus/Audio/Weiterleitung wurde entfernt.wav
new file mode 100644
index 0000000..afd9b8f
Binary files /dev/null and b/73_dynWeiterleitung_aus/Audio/Weiterleitung wurde entfernt.wav differ
diff --git a/73_dynWeiterleitung_aus/Audio/leer50ms.wav b/73_dynWeiterleitung_aus/Audio/leer50ms.wav
new file mode 100644
index 0000000..eea9512
Binary files /dev/null and b/73_dynWeiterleitung_aus/Audio/leer50ms.wav differ
diff --git a/73_dynWeiterleitung_aus/Main.flow b/73_dynWeiterleitung_aus/Main.flow
new file mode 100644
index 0000000..bb90814
--- /dev/null
+++ b/73_dynWeiterleitung_aus/Main.flow
@@ -0,0 +1,22 @@
+
+
+ 2.1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/73_dynWeiterleitung_aus/Output/Release/Audio/IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav b/73_dynWeiterleitung_aus/Output/Release/Audio/IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav
new file mode 100644
index 0000000..2e2b922
Binary files /dev/null and b/73_dynWeiterleitung_aus/Output/Release/Audio/IhreAnrufeWerdenAufDieFolgendeNummerWeitergeleitet-vicki.wav differ
diff --git a/73_dynWeiterleitung_aus/Output/Release/Audio/Weiterleitung wurde entfernt.wav b/73_dynWeiterleitung_aus/Output/Release/Audio/Weiterleitung wurde entfernt.wav
new file mode 100644
index 0000000..afd9b8f
Binary files /dev/null and b/73_dynWeiterleitung_aus/Output/Release/Audio/Weiterleitung wurde entfernt.wav differ
diff --git a/73_dynWeiterleitung_aus/Output/Release/Script/Main.cs b/73_dynWeiterleitung_aus/Output/Release/Script/Main.cs
new file mode 100644
index 0000000..f8437f5
--- /dev/null
+++ b/73_dynWeiterleitung_aus/Output/Release/Script/Main.cs
@@ -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, ICallflow, ICallflowProcessor
+ {
+ private bool executionStarted;
+ private bool executionFinished;
+ private bool disconnectFlowPending;
+
+ private BufferBlock eventBuffer;
+
+ private int currentComponentIndex;
+ private List mainFlowComponentList;
+ private List disconnectFlowComponentList;
+ private List errorFlowComponentList;
+ private List currentFlowComponentList;
+
+ private LogFormatter logFormatter;
+ private TimerManager timerManager;
+ private Dictionary 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("variableAssignmentExtensionNr");
+ variableAssignmentExtensionNr.VariableName = "project$.ExtensionNr";
+ variableAssignmentExtensionNr.VariableValueHandler = () => { return variableMap["session.ani"].Value; };
+ mainFlowComponentList.Add(variableAssignmentExtensionNr);
+ VariableAssignmentComponent variableAssignmentZielNr = scope.CreateComponent("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("mainAutoAddedFinalDisconnectCall");
+ DisconnectCallComponent errorHandlerAutoAddedFinalDisconnectCall = scope.CreateComponent("errorHandlerAutoAddedFinalDisconnectCall");
+ mainFlowComponentList.Add(mainAutoAddedFinalDisconnectCall);
+ errorFlowComponentList.Add(errorHandlerAutoAddedFinalDisconnectCall);
+ }
+
+ public Main()
+ {
+ this.executionStarted = false;
+ this.executionFinished = false;
+ this.disconnectFlowPending = false;
+
+ this.eventBuffer = new BufferBlock();
+
+ this.currentComponentIndex = 0;
+ this.mainFlowComponentList = new List();
+ this.disconnectFlowComponentList = new List();
+ this.errorFlowComponentList = new List();
+ this.currentFlowComponentList = mainFlowComponentList;
+
+ this.timerManager = new TimerManager();
+ this.timerManager.OnTimeout += (state) => eventBuffer.Post(new TimeoutEvent(state));
+ this.variableMap = new Dictionary();
+
+ 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 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("CreateCondition1");
+ mainFlowComponentList.Add(CreateCondition1);
+ CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(SetExtDNDDest.ReturnValue,0)); });
+ CreateCondition1.ContainerList.Add(scope.CreateComponent("conditionalComponentBranch1"));
+ TcxSetExtensionStatusComponent SetAvailable = scope.CreateComponent("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("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("conditionalComponentBranch2"));
+ TcxSetExtensionStatusComponent SetExtCustom2 = scope.CreateComponent("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("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 result = officeHours.IsActiveTime(nowDt);
+ return result.GetValueOrDefault(false);
+ }
+ }
+public class SetExtDNDDest1962110077ECCComponent : ExternalCodeExecutionComponent
+ {
+ public List Parameters { get; } = new List();
+ 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); }
+ }
+
+ }
+}
diff --git a/73_dynWeiterleitung_aus/Output/Release/_73_dynWeiterleitung_aus.zip b/73_dynWeiterleitung_aus/Output/Release/_73_dynWeiterleitung_aus.zip
new file mode 100644
index 0000000..46e6fd5
Binary files /dev/null and b/73_dynWeiterleitung_aus/Output/Release/_73_dynWeiterleitung_aus.zip differ
diff --git a/73_dynWeiterleitung_aus/README.md b/73_dynWeiterleitung_aus/README.md
new file mode 100644
index 0000000..d4a89a5
--- /dev/null
+++ b/73_dynWeiterleitung_aus/README.md
@@ -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#
+ z.B. Wtlg Nst10: *72,10#
+ z.B. Wtlg aus: *73
+- Fanvil BLF Tasten als indiv. Kurzwahl definieren :
+ z.B. Wtlg Handy: *72,0172123456#
+ z.B. Wtlg Nst10: *72,10#
+ z.B. Wtlg aus: *73
+- Snom (ungetestet) BLF Tasten als indiv. Kurzwahl definieren:
+ z.B. Wtlg Handy: *72;dtmf=0172123456#
+ z.B. Wtlg Nst10: *72;dtmf=10#
+ z.B. Wtlg aus: *73
+
+
+*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)
diff --git a/73_dynWeiterleitung_aus/setStateAndFWDestination.comp b/73_dynWeiterleitung_aus/setStateAndFWDestination.comp
new file mode 100644
index 0000000..b401a67
--- /dev/null
+++ b/73_dynWeiterleitung_aus/setStateAndFWDestination.comp
@@ -0,0 +1,43 @@
+
+
+ 2.1
+
+
+
+ strDestNo
+ true
+ true
+
+
+
+ strExtensionNo
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/99_Mailbox_NurAnsage/Main.flow b/99_Mailbox_NurAnsage/Main.flow
new file mode 100644
index 0000000..7809732
--- /dev/null
+++ b/99_Mailbox_NurAnsage/Main.flow
@@ -0,0 +1,51 @@
+
+
+ 2.1
+
+
+
+ ExtensionNo
+ false
+ true
+
+
+
+ ExtensionIVRPath
+ false
+ true
+
+
+
+ wavfile
+ false
+ true
+
+
+
+ wavfullpath
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/99_Mailbox_NurAnsage/Output/Release/Script/Main.cs b/99_Mailbox_NurAnsage/Output/Release/Script/Main.cs
new file mode 100644
index 0000000..2db5eab
--- /dev/null
+++ b/99_Mailbox_NurAnsage/Output/Release/Script/Main.cs
@@ -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, ICallflow, ICallflowProcessor
+ {
+ private bool executionStarted;
+ private bool executionFinished;
+ private bool disconnectFlowPending;
+
+ private BufferBlock eventBuffer;
+
+ private int currentComponentIndex;
+ private List mainFlowComponentList;
+ private List disconnectFlowComponentList;
+ private List errorFlowComponentList;
+ private List currentFlowComponentList;
+
+ private LogFormatter logFormatter;
+ private TimerManager timerManager;
+ private Dictionary 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("AssignVariable1");
+ AssignVariable1.VariableName = "callflow$.ExtensionNo";
+ AssignVariable1.VariableValueHandler = () => { return cGetDialedExtension1.DialedExtensionNo; };
+ mainFlowComponentList.Add(AssignVariable1);
+ VariableAssignmentComponent AssignVariable2 = scope.CreateComponent("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("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("PromptPlayback2");
+ PromptPlayback2.AllowDtmfInput = true;
+ PromptPlayback2.Prompts.Add(new AudioFilePrompt(() => { return Convert.ToString(cGetVMWavfile1.result_Fullfilename); }));
+ mainFlowComponentList.Add(PromptPlayback2);
+ DisconnectCallComponent DisconnectCall1 = scope.CreateComponent("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("mainAutoAddedFinalDisconnectCall");
+ DisconnectCallComponent errorHandlerAutoAddedFinalDisconnectCall = scope.CreateComponent("errorHandlerAutoAddedFinalDisconnectCall");
+ mainFlowComponentList.Add(mainAutoAddedFinalDisconnectCall);
+ errorFlowComponentList.Add(errorHandlerAutoAddedFinalDisconnectCall);
+ }
+
+ public Main()
+ {
+ this.executionStarted = false;
+ this.executionFinished = false;
+ this.disconnectFlowPending = false;
+
+ this.eventBuffer = new BufferBlock();
+
+ this.currentComponentIndex = 0;
+ this.mainFlowComponentList = new List();
+ this.disconnectFlowComponentList = new List();
+ this.errorFlowComponentList = new List();
+ this.currentFlowComponentList = mainFlowComponentList;
+
+ this.timerManager = new TimerManager();
+ this.timerManager.OnTimeout += (state) => eventBuffer.Post(new TimeoutEvent(state));
+ this.variableMap = new Dictionary();
+
+ 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 variableMap = componentVariableMap;
+ {
+ ExecuteCSharpCode1864467505ECCComponent ExecuteCSharpCode1 = new ExecuteCSharpCode1864467505ECCComponent("ExecuteCSharpCode1", callflow, myCall, logHeader);
+ mainFlowComponentList.Add(ExecuteCSharpCode1);
+ VariableAssignmentComponent AssignVariable1 = scope.CreateComponent("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 result = officeHours.IsActiveTime(nowDt);
+ return result.GetValueOrDefault(false);
+ }
+ }
+public class ExecuteCSharpCode1864467505ECCComponent : ExternalCodeExecutionComponent
+ {
+ public List Parameters { get; } = new List();
+ 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 variableMap = componentVariableMap;
+ {
+ FileManagementComponent ReadWriteFile1 = scope.CreateComponent("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("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("CreateCondition1");
+ mainFlowComponentList.Add(CreateCondition1);
+ CreateCondition1.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["callflow$.Profilename"].Value,"Available")); });
+ CreateCondition1.ContainerList.Add(scope.CreateComponent("cond1"));
+ VariableAssignmentComponent AssignVariable1 = scope.CreateComponent("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("conditionalComponentBranch1"));
+ VariableAssignmentComponent AssignVariable2 = scope.CreateComponent("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("conditionalComponentBranch2"));
+ VariableAssignmentComponent AssignVariable3 = scope.CreateComponent("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("conditionalComponentBranch3"));
+ VariableAssignmentComponent AssignVariable4 = scope.CreateComponent("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("conditionalComponentBranch4"));
+ VariableAssignmentComponent AssignVariable5 = scope.CreateComponent("AssignVariable5");
+ AssignVariable5.VariableName = "callflow$.Filename";
+ AssignVariable5.VariableValueHandler = () => { return variableMap["callflow$.wavCustom2"].Value; };
+ CreateCondition1.ContainerList[4].ComponentList.Add(AssignVariable5);
+ ConditionalComponent CreateCondition2 = scope.CreateComponent("CreateCondition2");
+ mainFlowComponentList.Add(CreateCondition2);
+ CreateCondition2.ConditionList.Add(() => { return Convert.ToBoolean(CFDFunctions.EQUAL(variableMap["callflow$.Filename"].Value,"")); });
+ CreateCondition2.ContainerList.Add(scope.CreateComponent("conditionalComponentBranch5"));
+ VariableAssignmentComponent variableAssignmentComponent1 = scope.CreateComponent("variableAssignmentComponent1");
+ variableAssignmentComponent1.VariableName = "callflow$.Filename";
+ variableAssignmentComponent1.VariableValueHandler = () => { return variableMap["callflow$.wavDefault"].Value; };
+ CreateCondition2.ContainerList[0].ComponentList.Add(variableAssignmentComponent1);
+ VariableAssignmentComponent AssignVariable6 = scope.CreateComponent("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 result = officeHours.IsActiveTime(nowDt);
+ return result.GetValueOrDefault(false);
+ }
+ }
+
+ }
+}
diff --git a/99_Mailbox_NurAnsage/Output/Release/VMNurAnsage.zip b/99_Mailbox_NurAnsage/Output/Release/VMNurAnsage.zip
new file mode 100644
index 0000000..73f1d45
Binary files /dev/null and b/99_Mailbox_NurAnsage/Output/Release/VMNurAnsage.zip differ
diff --git a/99_Mailbox_NurAnsage/README.md b/99_Mailbox_NurAnsage/README.md
new file mode 100644
index 0000000..d12e807
--- /dev/null
+++ b/99_Mailbox_NurAnsage/README.md
@@ -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, ...)
\ No newline at end of file
diff --git a/99_Mailbox_NurAnsage/VMNurAnsage.cfdproj b/99_Mailbox_NurAnsage/VMNurAnsage.cfdproj
new file mode 100644
index 0000000..e73535f
--- /dev/null
+++ b/99_Mailbox_NurAnsage/VMNurAnsage.cfdproj
@@ -0,0 +1,32 @@
+
+
+ 2.1
+
+
+
+
+
+
+
+
+ False
+ True
+ 0
+ 43
+ True
+ True
+ *99
+ None
+ None
+
+
+
+
+ us-east-2
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/99_Mailbox_NurAnsage/cGetDialedExtension.comp b/99_Mailbox_NurAnsage/cGetDialedExtension.comp
new file mode 100644
index 0000000..fdf6443
--- /dev/null
+++ b/99_Mailbox_NurAnsage/cGetDialedExtension.comp
@@ -0,0 +1,28 @@
+
+
+ 2.1
+
+
+
+ DialedExtensionNo
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/99_Mailbox_NurAnsage/cGetVMWavfile.comp b/99_Mailbox_NurAnsage/cGetVMWavfile.comp
new file mode 100644
index 0000000..eca2ed9
--- /dev/null
+++ b/99_Mailbox_NurAnsage/cGetVMWavfile.comp
@@ -0,0 +1,112 @@
+
+
+ 2.1
+
+
+
+ IVRPath
+ true
+ true
+
+
+
+ Profilename
+ true
+ true
+
+
+
+ result_Fullfilename
+ true
+ true
+
+
+
+ wavDefault
+ Private
+ true
+ true
+
+
+
+ wavAvailable
+ Private
+ true
+ true
+
+
+
+ wavAway
+ Private
+ true
+ true
+
+
+
+ wavOoO
+ Private
+ true
+ true
+
+
+
+ wavCustom1
+ Private
+ true
+ true
+
+
+
+ wavCustom2
+ Private
+ true
+ true
+
+
+
+ Filename
+ Private
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 6320540..fa27be5 100644
--- a/README.md
+++ b/README.md
@@ -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#
+ z.B. Wtlg Nst10: *72,10#
+ z.B. Wtlg aus: *73
+- Fanvil BLF Tasten als indiv. Kurzwahl definieren :
+ z.B. Wtlg Handy: *72,0172123456#
+ z.B. Wtlg Nst10: *72,10#
+ z.B. Wtlg aus: *73
+- Snom (ungetestet) BLF Tasten als indiv. Kurzwahl definieren:
+ z.B. Wtlg Handy: *72;dtmf=0172123456#
+ z.B. Wtlg Nst10: *72;dtmf=10#
+ z.B. Wtlg aus: *73
+
+
+*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)
diff --git a/RELEASE/VMNurAnsage.zip b/RELEASE/VMNurAnsage.zip
new file mode 100644
index 0000000..73f1d45
Binary files /dev/null and b/RELEASE/VMNurAnsage.zip differ
diff --git a/RELEASE/_39_Profilstatus_mitExtension.zip b/RELEASE/_39_Profilstatus_mitExtension.zip
new file mode 100644
index 0000000..cf06f14
Binary files /dev/null and b/RELEASE/_39_Profilstatus_mitExtension.zip differ
diff --git a/RELEASE/_721_dynWeiterleitung_mitExtension_an.zip b/RELEASE/_721_dynWeiterleitung_mitExtension_an.zip
new file mode 100644
index 0000000..5175d96
Binary files /dev/null and b/RELEASE/_721_dynWeiterleitung_mitExtension_an.zip differ
diff --git a/RELEASE/_72_dynWeiterleitung_an.zip b/RELEASE/_72_dynWeiterleitung_an.zip
new file mode 100644
index 0000000..867b4a4
Binary files /dev/null and b/RELEASE/_72_dynWeiterleitung_an.zip differ
diff --git a/RELEASE/_73_dynWeiterleitung_aus.zip b/RELEASE/_73_dynWeiterleitung_aus.zip
new file mode 100644
index 0000000..46e6fd5
Binary files /dev/null and b/RELEASE/_73_dynWeiterleitung_aus.zip differ