V20 - Update 3 BETA - Verbessert die Administrator- und Systemleistung

MarcusK_3CX

Teammitglied
Mitglied seit
24. März 2023
Beiträge
524

Verbesserte Remote-Archivierung, Anruftranskriptionen und Massenbearbeitung von Nutzern.​

Update 3 ist nach der erfolgreichen ALPHA-Veröffentlichung vor einigen Wochen nun bereit für die BETA.

Update 3 BETA - Store für Anrufbearbeitungskripte​

Neu in der Beta ist die Möglichkeit, schnell ein fertiges Skri...
Original-Blogpost weiterlesen
 
  • Like
Reaktionen: fxbastler
Gratulation - die Idee mit dem Store ist grundsätzlich hervorragend!

Das ja die alte Funktion der Statusumschaltung der gesamten PBX per #64x ja mit v20 nicht mehr verfügbar ist hab ihr unter
https://www.3cx.com/blog/releases/v20-call-control-api/
eine Notlösung geschaffen die akzeptabel funktioniert.

Es wäre nun zu hoffen, dass diese Funktionen sehr bald im Store Einzug finden zu mal sie - wenn man Sie über die Scripts installiert - eben in den Anrufscripten auftauchen. Dürfte somit kein Riesen-Aufwand für Euch sein.
 
  • Like
Reaktionen: MarcusK_3CX
Das dort dokumentierte Skript von 3CX für die Statusumschaltung der Abteilung könntest du direkt verwenden oder du schaust dir diesen Thread an.
 
  • Like
Reaktionen: MarcusK_3CX
Hallo.

Mir ist aufgefallen, dass nach dem Update3beta die Funktion "Abgemeldete Nebenstellen anzeigen" aktiviert ist, obwohl es vorher deaktiviert war.
 
Hallo zusammen, ich habe eine 3CX in einem Friseursalon auf einem kleinen Windows PC eingerichtet. Wenn ich auf V20 migrieren will muss ich laut Anleitung Split DNS einrichten. Da ist aber keine Firewall, Split DNS ist doch nur bei Firewall betrieb nötig richtig?
 
@spok was für ein Router sitzt denn davor? Am besten für solche Fragen ein eigenes Thema aufmachen und nicht hier den Produkt Info Bereich dafür nehmen.
 
eine Fritzbox 7590, ich kann keine neuen Treads eröffnen. Da steht das ich keine Berechtigung dafür habe
 
@spok Forumsuche nutzen bitte dann findet man das z.B. Split-DNS ist Voraussetzung

Edit: Pack auf den PC Proxmox als Hypervisor, Pihole in einen LXC und eine Debian3cx in eine VM dann Anleitungen befolgen. und happy sein.
 
Zuletzt bearbeitet:
ok aber wenn ich nur 1 IP Telefon betreibe welches intern angeschossen wird im LAN und nichts weiter. Brauche ich dann Split DNS?
 
Hallo Mr. spok.

Ja, weil das Telefon sich per FQDN an die Anlage binden mòchte und normalerweise wird da die òffentliche IP vom DNS aufgelòst, das Telefon muss aber an der internen IP binden.

Externes Device nimmt externe IP und internes Device nimmt interne IP, doch beide mùssen den FQDN verwenden, daher Split-DNS.
Der interne DNS Server muss also beim FQDN die interne IP zurùck melden.
 
ok aber wenn ich nur 1 IP Telefon betreibe welches intern angeschossen wird im LAN und nichts weiter. Brauche ich dann Split DNS?
Sicher. Es steht nicht ohne Grund in den Installationsvoraussetzungen.
 
  • Like
Reaktionen: dj-melo
eine Fritzbox 7590, ich kann keine neuen Treads eröffnen. Da steht das ich keine Berechtigung dafür habe
Was hat die FritzBox denn für Aufgaben wenn eine 3CX im Einsatz ist? Ich würde sonst die FritzBox tauschen gegen einen anderen Router z.B. DrayTek der kann LAN DNS
 
Warum braucht man denn überhaupt eine Anlage, wenn nur 1 IP Telefon im Einsatz ist? Warum denn nicht an der FRITZ!Box betreiben?
 
Oder die Nummer direkt im Telefon registrieren, je nach Anspruch.
 
Hallo zusammen,

auf die Gefahr hin dass ich etwas überlesen habe:
Ist es richtig, dass ich in der Beta die Sprachpakete nicht installieren (System->Updates->Ansagensatz) kann oder habe ich da ein anderes (lokales) Problem mit meiner Instanz?
"Fehler: Interner Serverfehler ist aufgetreten. Bitte kontaktieren Sie den Administrator"
 
Da hast du ein anderes Problem, die Beta funktioniert einwandfrei.
 
Bin neugierig, kann an den Store für die CFD's von Extern einsehen oder geht das nur via des Button's in der V20 U3 ?
Habe das U3 Beta Update noch nicht.
 
kann an den Store für die CFD's von Extern einsehen
Was genau ist damit gemeint: der Store?

Das sollte z.B. nach Authentifizierung mittels REST API auch möglich sein, zum Testen auch gern per Powershell. Dort gibt es zumindest einen Endpunkt CallFlowScripts bzw. in der YAML einen Endpunkt CallFlowApps. Das geht auch mit einer akt. v20u2.
 
  • Like
Reaktionen: fxbastler
New in the beta is the ability to quickly add a ready script from our call processing script store.
Aaah, danke, den habe ich nicht bedacht.

Die Skripte sind aktuell bei einer v20.0.3.762 Beta wie folgt:
1726585073712.png

C#:
/*
 * User Input IVR Script - Use code with caution!
 * This script handles incoming calls to an Interactive Voice Response (IVR) system.
 * When a call reaches the IVR, it prompts the caller to enter a PIN code.
 * Based on the entered PIN, the call is routed to a predefined destination.
 *
 * INSTRUCTIONS
 * - To specify a PIN and its corresponding destination, modify the entries in the `UserInputMap` dictionary (Line 29-32).
 * - Example: { "5678", "104" } maps PIN "5678" to extension "104".
 */

#nullable disable
using CallFlow;
using System;
using System.Threading;
using System.Threading.Tasks;
using TCX.Configuration;
using TCX.PBXAPI;
using System.Collections.Generic;

namespace dummy
{
    public class UserInputIVRSample : ScriptBase<UserInputIVRSample>
    {
        // Mapping of PIN codes to destinations
        private readonly Dictionary<string, string> UserInputMap = new Dictionary<string, string>
        {
            { "1234", "100" },
            { "2345", "101" },
            { "3456", "102" },
            { "", "103" } // No input
        };

        // DTMF input events handler
        private void OnDTMFInput(char inputChar, TaskCompletionSource<string> inputTaskSource, ref string inputBuffer, Timer timer, int interdigitTimeout)
        {
            if (inputChar == '#')
            {
                timer?.Change(Timeout.Infinite, Timeout.Infinite); // Stop timer
                MyCall.Info($"User input is '{inputBuffer}'");
                inputTaskSource.TrySetResult(inputBuffer); // Set result
            }
            else
            {
                timer?.Change(interdigitTimeout, Timeout.Infinite); // Reset timer
                inputBuffer += inputChar; // Append character
            }
        }

        // Play prompt and get caller input
        private async Task<string> GetUserInputAsync(int interdigitTimeout, int inputTimeout)
        {
            string inputBuffer = "";
            var inputTaskSource = new TaskCompletionSource<string>();
            var timer = new Timer(_ => OnDTMFInput('#', inputTaskSource, ref inputBuffer, null, 0), null, inputTimeout, Timeout.Infinite);
            MyCall.OnDTMFInput += (char inputChar) => OnDTMFInput(inputChar, inputTaskSource, ref inputBuffer, timer, interdigitTimeout);

            try
            {
                await MyCall.PlayPrompt(null, new[] { "PLSENTER", "PIN", "THENPRESPND" }, PlayPromptOptions.ResetBufferAtStart | PlayPromptOptions.CancelPlaybackAtFirstChar);
                MyCall.Info("Prompt played");
                return await inputTaskSource.Task;
            }
            finally
            {
                timer.Dispose();
                MyCall.OnDTMFInput -= (char inputChar) => OnDTMFInput(inputChar, inputTaskSource, ref inputBuffer, timer, interdigitTimeout);
            }
        }

        // Handle call as a detached task
        public override async void Start()
        {
            try
            {
                await Task.Run(async () =>
                {
                    try
                    {
                        MyCall.Debug($"Script start delay: {DateTime.UtcNow - MyCall.LastChangeStatus}");
                        MyCall.Debug($"Incoming connection {MyCall}");
                        var ps = MyCall.PS as PhoneSystem;
                        CallControlResult lastResult = null;
                        bool scriptCompleted = false;
                        int attempts = 3;

                        if (await MyCall.AssureMedia().ContinueWith(task =>
                        {
                            MyCall.SetBackgroundAudio(true, new[] { ps.GetParameterValue("MUSICONHOLDFILE") });
                            return task.Result;
                        }))
                        {
                            while (attempts-- > 0 && ExecutionMode == ScriptExecutionMode.Active)
                            {
                                string userInput = null;
                                try
                                {
                                    lastResult = await GetUserInputAsync(2000, 10000)
                                        .ContinueWith(inputTask =>
                                        {
                                            userInput = inputTask.Result;
                                            return MyCall.RouteToAsync(new DestinationStruct(ps.GetDNByNumber(UserInputMap[userInput])));
                                        }).Unwrap()
                                        .ContinueWith(resultTask => resultTask.Result, TaskContinuationOptions.OnlyOnRanToCompletion);

                                    scriptCompleted = true;
                                    break;
                                }
                                catch (PBXIsNotConnected ex)
                                {
                                    MyCall.Error($"Call control API service is not available:\n{ex}");
                                }
                                catch (OperationFailed ex)
                                {
                                    MyCall.Error($"Failed to deliver the call - {userInput}={ex}");
                                }
                                catch (TaskCanceledException ex)
                                {
                                    MyCall.Trace($"Task was cancelled: {ex}");
                                }
                                catch (Exception ex)
                                {
                                    MyCall.Error($"Script logic exception: {ex}");
                                }
                            }
                        }

                        MyCall.Info("UserInputIVRSample Exit script");
                        MyCall.Return(scriptCompleted);
                    }
                    catch
                    {
                        MyCall.Return(false);
                    }
                });
            }
            catch
            {
                MyCall.Return(false);
            }
        }
    }
}
C#:
/*
 * Play Holiday Prompts - Use code with caution!
 * Requires holidays and prompts to be configured on department office hours
 * Requires trunk to be part of the department and holidays configured at department level
 * Based on the entered PIN, the call is routed to a predefined destination.
 * Script intercepts inbound calls on a trunk and checks if it is a holiday in Office hours - Department - Office holidays
 * If the day is set as a holiday and a prompt is configured, the prompt will be played and call wil be routed to the holiday routing destination
 * If there is no prompt, the prompt will be skipped
*/


#nullable disable
using CallFlow;
using CallFlow.CFD;
using TCX.Configuration;
using System.Threading.Tasks;
using TCX.PBXAPI;
namespace dummy
{
    public class PlayHolidayPromptBeforeRouting : ScriptBase<PlayHolidayPromptBeforeRouting>
    {
        string GetRealPromptFileName(OfficeHoliday holiday)
        {
            return holiday?.HolidayPrompt;
        }
      
        public override async Task<bool> StartAsync()
        {
          
            PhoneSystem ps = (PhoneSystem)MyCall.PS;
            if (MyCall.Caller.DN is ExternalLine externalLine)
            {
                var trunkTimeBasedroute = MyCall.Caller.DN.GetTimeBasedRoutingInfo();
                if (
                    trunkTimeBasedroute.reason == CallControlAPI.DivertReason.Holiday
                    && GetRealPromptFileName(trunkTimeBasedroute.holiday) is string thePath
                    && !string.IsNullOrWhiteSpace(thePath)
                    )
                {
                    await MyCall.AssureMedia()
                    .ContinueWith(x => MyCall.PlayPrompt(null, new[] { thePath }, PlayPromptOptions.Blocked))
                                .Unwrap();
                }
            }
            return false;
        }
    }
}
C#:
/*
 * Time Base Call Routing - Use code with caution!
 * Calls will be intercepted and redirected to the specified DestinationDN during the following times:
 * Monday to Sunday: 5:30 PM to 7:00 AM
 * The current destination DN is set to "801" modify destination to any system extension or extension you want.
 *
 * INSTRUCTIONS
 * - Configure Date & Time (Line 31)
 * - Change the destination DN, and modify the value of the constant DestinationDN to any destination you want to route call (Line 26).
 */

#nullable disable
using CallFlow;
using System;
using System.Threading;
using System.Threading.Tasks;
using TCX.Configuration;
using TCX.PBXAPI;
using System.Collections.Generic;
using System.Linq;
using CallFlow.CFD;

namespace interceptcall
{
    public class InterceptInboundCall : ScriptBase<InterceptInboundCall>
    {
        // The destination DN to which the call will be redirected
        const string DestinationDN = "801";
      
        // Define a schedule for when calls should be intercepted
        static readonly Schedule schedule = new Schedule(RuleHoursType.SpecificHours)
        {
            { DayOfWeek.Monday, new Schedule.PeriodOfDay(TimeSpan.FromHours(17.5), TimeSpan.FromHours(24)) },
            { DayOfWeek.Monday, new Schedule.PeriodOfDay(TimeSpan.FromHours(0), TimeSpan.FromHours(7)) },
            { DayOfWeek.Tuesday, new Schedule.PeriodOfDay(TimeSpan.FromHours(17.5), TimeSpan.FromHours(24)) },
            { DayOfWeek.Tuesday, new Schedule.PeriodOfDay(TimeSpan.FromHours(0), TimeSpan.FromHours(7)) },
            { DayOfWeek.Wednesday, new Schedule.PeriodOfDay(TimeSpan.FromHours(17.5), TimeSpan.FromHours(24)) },
            { DayOfWeek.Wednesday, new Schedule.PeriodOfDay(TimeSpan.FromHours(0), TimeSpan.FromHours(7)) },
            { DayOfWeek.Thursday, new Schedule.PeriodOfDay(TimeSpan.FromHours(17.5), TimeSpan.FromHours(24)) },
            { DayOfWeek.Thursday, new Schedule.PeriodOfDay(TimeSpan.FromHours(0), TimeSpan.FromHours(7)) },
            { DayOfWeek.Friday, new Schedule.PeriodOfDay(TimeSpan.FromHours(17.5), TimeSpan.FromHours(24)) },
            { DayOfWeek.Friday, new Schedule.PeriodOfDay(TimeSpan.FromHours(0), TimeSpan.FromHours(7)) },
            { DayOfWeek.Saturday, new Schedule.PeriodOfDay(TimeSpan.FromHours(17.5), TimeSpan.FromHours(24)) },
            { DayOfWeek.Saturday, new Schedule.PeriodOfDay(TimeSpan.FromHours(0), TimeSpan.FromHours(7)) },
            { DayOfWeek.Sunday, new Schedule.PeriodOfDay(TimeSpan.FromHours(17.5), TimeSpan.FromHours(24)) },
            { DayOfWeek.Sunday, new Schedule.PeriodOfDay(TimeSpan.FromHours(0), TimeSpan.FromHours(7)) }
        };

        public override async void Start()
        {
            // Handle calls as a detached task, catching all exceptions.
            try
            {
                await Task.Run(async () =>
                {
                    try
                    {
                        MyCall.Debug($"Script start delay: {DateTime.UtcNow - MyCall.LastChangeStatus}");
                        MyCall.Debug($"Incoming connection {MyCall} from {MyCall.Caller}");
                        bool intercepted = false;
                        var ps = MyCall.PS as PhoneSystem;

                        if (MyCall.Caller.DN is ExternalLine externalLine)
                        {
                            var DIDNumber = MyCall.Caller["inbound_did"];
                            var CallerID = MyCall.Caller.CallerID;
                            var currentTime = externalLine.Now(out var utc, out var timezone, out var groupmode);

                            // Intercept calls during the specified schedule
                            if (schedule.IsActiveTime(currentTime))
                            {
                                string[] DIDs = { "*" }; // Allow all DIDs
                                string[] Callers = { "*" }; // Allow all Callers
                                var destination_struct = new DestinationStruct(ps.GetDNByNumber(DestinationDN));

                                // Check if the call's DID and CallerID match the interception criteria
                                if ((DIDs.Contains(DIDNumber) || DIDs.Contains("*")) &&
                                    (Callers.Contains(CallerID) || Callers.Contains("*")))
                                {
                                    try
                                    {
                                        var result = await MyCall.RouteToAsync(destination_struct);
                                        MyCall.Info($"{CallerID} -> {DIDNumber} has been redirected to {DestinationDN} ({result})");
                                        intercepted = true;
                                    }
                                    catch (Exception ex)
                                    {
                                        MyCall.Info($"{CallerID} -> {DIDNumber}: interception failed. '{DestinationDN}' is not reachable: {ex}");
                                    }
                                }
                                else
                                {
                                    MyCall.Info($"{CallerID} -> {DIDNumber}@{currentTime}: Default CallerID/DID based routing will be applied");
                                }
                            }
                        }

                        MyCall.Return(intercepted);
                    }
                    catch (Exception ex)
                    {
                        MyCall.Error($"Script execution failed: {ex}");
                        MyCall.Return(false);
                    }
                });
            }
            catch (Exception ex)
            {
                MyCall.Error($"Task execution failed: {ex}");
                MyCall.Return(false);
            }
        }
    }
}
 
  • Like
Reaktionen: Ben04

Statistik des Forums

Themen
44.455
Beiträge
232.855
Mitglieder
78.351
Neuestes Mitglied
onno68