如何创建呼叫处理脚本
介绍
呼叫处理脚本是 V20 的一项强大新功能。它允许您捕获呼叫并使用标准 C# 代码进行处理,从而为您提供无限的可能性来分析呼叫并应用自定义逻辑。以下是一些示例:
- 分析来电显示并分配特定客服人员
- 根据来电显示进行客户查找并相应路由。
- 检查时间和日期,并据此处理来电。
- 检查日期并根据日期播放提示音。
调用呼叫处理脚本
编写脚本之前,您必须考虑脚本的触发位置和触发方式。您可以为其配置拨号代码,或者通过分配 DID 号码或根据特定变量将呼叫转发到脚本。
在Update 2 中,您将能够对每个进入 SIP 中继的呼入呼叫触发呼叫!
确定脚本的触发方式后,您需要筛选呼叫并编写呼叫处理逻辑。
呼叫处理 API 概述
该 API 由三个核心方法组成,这些方法会将呼叫传递给新的目的地。
方法组 Task<CallControlResult> RouteToAsync(this ActiveConnection ac, <Destination>)
此方法创建一条路由,该路由绑定到指定的连接 ac
- ac - 由 RoutePoint(路由点)拥有的活动连接。当新目的地应答时,其连接将替换 ac。(涉及 RoutePoint 的参与)。
- 脚本应处理任务失败。脚本可以简单地调用 MyCall.Return 来终止其与主叫方的连接。
- 脚本可以根据需要尝试建立尽可能多的路由,但第一个被应答的路由将取消所有其他路由,并将替换 RoutePoint 在呼叫中的参与(将终止 RoutePoint 的呼叫)。
- RouteToAsync 可以在 RoutePoint 连接的任何状态下执行。因此,路由点可以在与主叫方通信时执行后台路由(播放提示音、处理 DTMF 信号等)。
- 当任务成功时,呼叫将从 RoutePoint 撤回(MyCall 被断开),新目的地继续处理呼叫。
方法组 Task<CallControlResult> DivertAsync(this ActiveConnection ac,<Destination>)
方法将呼叫转移到新目的地,而不在 RoutePoint 上建立(应答)连接(即在 RoutePoint 振铃)。
- 如果呼叫已与 RoutePoint 建立,该方法将失败,应改用 RouteToAsync 或 ReplaceWithAsync。
- 由 RoutePoint 拥有的活动连接(处于振铃状态)将被新目的地替换,并且 RoutePoint 将从呼叫中断开。
- 如果路由点不需要与主叫方交互,此方法非常有用。
- 如果任务失败,脚本可以继续处理与主叫方的连接。
- 当任务成功时,呼叫将从 RoutePoint 撤回(MyCall 连接终止),新目的地开始呼叫处理。RoutePoint 脚本切换到整理模式,需要完成其自身的任务。
方法组 Task<CallControlResult> ReplaceWithAsync(this ActiveConnection ac,<Destination>)
也称为“盲转”法。
- 仅在已连接模式下允许(RoutePoint 已接听呼叫并与用户交互)。
- 主叫方将被置于保持状态。
- 如果目的地不可达,任务将失败。
- 任务失败后,脚本可以继续处理呼叫(如果主叫方仍与 RoutePoint 保持连接)。
- 当任务成功时,脚本连接(ICallHandler.MyCall)终止,脚本与呼叫处理分离(新参与者将接管主叫方)。
示例呼叫处理脚本
此示例演示了如何创建自定义路由点以及如何对其进行编程:
- C# 代码提供给 RoutePoint 的基本结构
- TCX.PBXAPI.CallControlAPI 扩展方法在 ActiveConnection (ICall) 接口上的基本用法
- CallFlowScriptingCore 提供的 MyCall 对象的基本操作
- 配置(PBX 参数)的基本操作
- 使用 ActiveConnection 对象的 RouteToAsync 扩展方法
RoutePoint 功能示例:
- 路由点只接受通过话机(分机)使用盲转发送的呼叫。直接呼叫被拒绝。
- 任何分机都可以同时将不限数量的呼叫转接至此 RoutePoint(每个呼叫单独处理)。
- 15 秒后,呼叫将直接返回到转接方(分机)(不应用转发)。如果返回的呼叫在 15 秒内未被应答,则呼叫取消并在 15 秒后再次尝试。
- 主叫方听到的保持音乐与 PBX 上为“停泊”功能配置的音乐相同。
如何运行
- 创建一个具有任意号码的路由点(RoutingPoint),例如 #101,并将 RoutePoint.ScriptCode 属性设置为下面的文本(当前 UI 仍不允许使用自定义(手写代码)创建路由点,它需要某种 zip 文件,但对于简单脚本来说这并不是必需的)。
- RoutePoint 应出现在相应列表中(目前是“CFD 应用程序”),并带有绿点(此代码编译不应失败)。
- 然后:
- 如果有任何分机将其呼叫转接到号码 #101,该呼叫将在 15 秒后被返回。
- 主叫方将听到为“停车”功能配置的保持音乐。(脚本使用此设置,但可以修改代码为主叫方生成其他内容)。
- 如果返回的呼叫未被应答,RoutePoint 将在上一次尝试后 15 秒再次尝试(并一直重复),直到主叫方挂断呼叫,或者原始转接方应答(或其呼叫被接听)。
代码注释
- “脚本化”对象的代码基于(使用、实现和/或继承)
- CallFlow namespace
- CallFlow.ICall
- CallFlow.ICallHandler
- CallFlow.ICallHandlerEx
- CallFlow.ScriptBase<T>
- “脚本对象”类必须继承CallFlow.ScriptBase<T>并根据实例需要实现所有抽象方法。
- 当 ScriptingHost 使用 ICallHandler.Start 方法执行呼叫处理程序时,该对象开始运行。
- 脚本必须以 ICall.Return 显式结束。
- ICall.Start 方法的推荐实现是 async void,它运行分离的任务(必须捕获所有异常)。
- 脚本实现必须仅控制由脚本主机公开的 MyCall 对象。这是呼叫脚本会话的唯一主体。
- 当 MyCall(路由点在呼叫中的参与)结束时,脚本实现应进行整理并结束其会话。
- 呼叫流程脚本不是监视系统配置或任何外部资源的方式。
- 它不是监视系统中所有呼叫的方式。
- 它只是一个 RoutePoint 逻辑,可以与其他呼叫流程集成。
- 换句话说:脚本处理与 RoutePoint 连接的其中一个呼叫,但绝不发起新的呼叫。
- 路由 API 封装在静态类 TCX.PBXAPI.CallControlAPI 中,该类公开了以下对象的扩展方法:
- TCX.Configuration.ActiveCannection
- TCX.Configuration.DN
- TCX.Configuration.RegistrarRecord
示例代码
#nullable disable using CallFlow; using System; using System.Threading; using System.Threading.Tasks; using TCX.Configuration; using TCX.PBXAPI; namespace dummy { public class ParkingRoutePointSample : ScriptBase<ParkingRoutePointSample> { async Task<CallControlResult> ProcessAutoPickup(RoutePoint sp, DestinationStruct returnTo, CancellationToken token) { while (true) try { return await Task.Delay(TimeSpan.FromSeconds(15), token).ContinueWith(x => { MyCall.Trace("{0} - automatic redirection of the call from {1}.{2} to '{3}'", MyCall.DN, MyCall.Caller?.CallerID, MyCall.Caller?.DN, returnTo); return MyCall.RouteToAsync(new RouteRequest { RouteTarget = returnTo, TimeOut = TimeSpan.FromSeconds(15) //will ring until failure } ); } , TaskContinuationOptions.NotOnCanceled).Unwrap(); } catch (OperationFailed ex) { MyCall.Trace("Automatic redirection failed: {0}", ex.TheResult); MyCall.Trace("Continue hold call from {0}({1}) on {2}", MyCall.Caller?.CallerID, MyCall.Caller?.DN, MyCall.DN); continue; } } PhoneSystem ps = null; /// <summary> /// /// </summary> public override async void Start() { await Task.Run(async () => { try { MyCall.Debug($"Script start delay: {DateTime.UtcNow - MyCall.LastChangeStatus}"); MyCall.Debug($"Incoming connection {MyCall}"); ps = MyCall.PS as PhoneSystem; CallControlResult lastresult = null; DN referredBy = null; RoutePoint thisPark = null; string callerID = ""; DN callerDN = null; bool scriptCompleted = true; try { referredBy = MyCall.ReferredByDN?.GetFullSnapshot() as Extension; thisPark = MyCall.DN?.Clone() as RoutePoint; callerID = MyCall.Caller?.CallerID; callerDN = MyCall.Caller?.DN?.Clone() as DN; MyCall.Trace( "Parked call from {0}({1}) on {2}", callerID, callerDN, thisPark ); if (referredBy == null) { MyCall.Trace("{0} rejects call from {1}. Reason: No referrer specified", thisPark, callerDN); return; } var cancelationToken = new CancellationTokenSource(); MyCall.OnTerminated += () => { cancelationToken.Cancel(); }; lastresult = await MyCall.AssureMedia().ContinueWith( x => { if(!string.IsNullOrWhiteSpace(ps.GetParameterValue("PARK_MOH_SOURCE"))) MyCall.SetBackgroundAudio(true, new string[] { ps.GetParameterValue("PARK_MOH_SOURCE") }); else MyCall.SetBackgroundAudio(true, new string[] { ps.GetParameterValue("MUSICONHOLDFILE") }); return ProcessAutoPickup(thisPark, new DestinationStruct(referredBy), cancelationToken.Token); }, TaskContinuationOptions.OnlyOnRanToCompletion).Unwrap(); } catch (PBXIsNotConnected ex) { MyCall.Error($"Call control API is not available:\n{ex}"); scriptCompleted = false; } catch (TaskCanceledException) { MyCall.Trace($"Call was disconnected from parking place"); } catch (Exception ex) { MyCall.Error($"Parking failure:\n{ex}"); scriptCompleted = false; } finally { try { MyCall.Info("Call from {0}({1}) parked by {2} on {3} finished with result={4}", callerID, callerDN, referredBy, thisPark, lastresult?.ToString() ?? "terminated"); } catch (Exception ex) { MyCall.Error($"SharedParkingFlow finalize exception {ex}"); } MyCall.Return(scriptCompleted); } } catch { MyCall.Return(false); } }); } } }
另请参阅
最后更新时间
本文件最后更新日期为2024年3月5日。


