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