了解 FreePBX 并将其与 Bitrix24 等集成

Bitrix24 是一个巨大的组合,它结合了 CRM、工作流、会计和许多其他经理真正喜欢而 IT 员工不太喜欢的东西。 该门户被许多中小型公司使用,包括小型诊所、制造商甚至美容院。 管理者“最爱”的主要功能是电话和CRM的整合,任何来电都会立即记录在CRM中,创建客户名片,来电时会显示客户信息,你可以立即看到他是谁,他在干什么可以卖多少,他欠多少钱。 但是来自 Bitrix24 的电话及其与 CRM 的集成需要花费金钱,有时甚至是很多。 在文章中我会告诉你与开放工具和流行的IP PBX集成的经验 FreePBX的,还要考虑各个部分的工作逻辑

我在一家销售和配置、集成 IP 电话的公司担任外包商。 当我被问到我们是否可以为这家公司提供一些东西来将 Bitrix24 与客户拥有的 PBX 以及各种 VDS 公司的虚拟 PBX 集成时,我去了谷歌。 当然他给了我一个链接 哈布尔文​​章,其中有描述和 github,一切似乎都有效。 但是当尝试使用这个解决方案时,发现 Bitrix24 已经和以前不一样了,很多东西需要重做。 此外,FreePBX 对你来说不是一个简单的星号,在这里你需要考虑如何在配置文件中结合易用性和硬核拨号方案。

我们研究工作的逻辑

所以对于初学者来说,它应该如何运作。 当 PBX 从外部接收到呼叫时(来自供应商的 SIP INVITE 事件),拨号方案(拨号方案,拨号方案)的处理开始 - 呼叫的规则和顺序。 从第一个数据包中,您可以获得很多信息,然后可以在规则中使用这些信息。 分析器是研究 SIP 内部结构的绝佳工具 sngrep (链接) 可以通过 apt install/yum install 等简单地安装在流行的发行版中,但也可以从源代码构建。 我们看看sngrep中的调用日志

了解 FreePBX 并将其与 Bitrix24 等集成

在一个简化的形式中,拨号方案只处理第一个数据包,有时也在对话过程中,呼叫转移,按钮按下(DTMF),各种有趣的事情,如 FollowMe,RingGroup,IVR 等等。

邀请包内有什么

了解 FreePBX 并将其与 Bitrix24 等集成

实际上,大多数简单的拨号方案都使用前两个字段,整个逻辑围绕着 DID 和 CallerID。 DID - 我们在哪里打电话,CallerID - 谁在打电话。

但毕竟,我们有一家公司,而不是一部电话——这意味着 PBX 很可能在城市号码(Ring Group)、IVR(你好,你打电话给......按一个用于...),应答机(短语),时间条件,转发到其他号码或单元格(FollowMe,转发)。 这意味着很难明确地确定谁将实际接听电话,以及当电话到达时将与谁进行对话。 这是在我们客户的 PBX 中开始典型呼叫的示例

了解 FreePBX 并将其与 Bitrix24 等集成

呼叫成功进入 PBX 后,它会在不同的“上下文”中通过拨号方案。 从 Asterisk 的角度来看,上下文是一组编号的命令,每个命令都包含一个按所拨号码的过滤器(称为 exten,对于初始阶段的外部呼叫 exten=DID)。 拨号方案行中的命令可以是任何东西 - 内部功能(例如,呼叫内部用户 - Dial(), 放下电话—— Hangup()), 条件运算符 (IF, ELSE, ExecIF 等),过渡到此上下文的其他规则(Goto, GotoIF),以函数调用(Gosub,Macro)的形式过渡到其他上下文。 一个单独的指令 include имя_контекста, 它将来自另一个上下文的命令添加到当前上下文的末尾。 通过 include 包含的命令总是被执行 当前上下文的命令。

FreePBX 的整个逻辑是建立在通过包含和调用 Gosub、宏和处理程序处理程序将不同的上下文相互包含在一起的基础上的。 考虑传入 FreePBX 呼叫的上下文

了解 FreePBX 并将其与 Bitrix24 等集成

调用从上到下依次遍历所有上下文,在每个上下文中都可以调用其他上下文,如宏(Macro)、函数(Gosub)或只是转换(Goto),所以被调用的真实树只能在日志中被跟踪。

典型 PBX 的典型设置图如下所示。 呼叫时,在传入路由中搜索 DID,检查临时条件,如果一切正常,则启动语音菜单。 从那里,通过按下按钮 1 或超时,退出到拨号操作员组。 通话结束后,hangupcall 宏被调用,之后除了特殊的处理程序(挂断处理程序)外,拨号方案中什么也做不了。

了解 FreePBX 并将其与 Bitrix24 等集成

在这个呼叫算法中,我们应该在哪里提供有关 CRM 呼叫开始的信息,从哪里开始录音,在哪里结束录音并将其与有关 CRM 呼叫的信息一起发送?

与外部系统集成

什么是 PBX 和 CRM 集成? 这些是在这两个平台之间转换数据和事件并将它们发送给彼此的设置和程序。 独立系统最常见的通信方式是通过 API,而最流行的访问 API 的方式是 HTTP REST。 但不适用于星号。

Asterisk 内部是:

  • AGI - 同步调用外部程序/组件,主要用于拨号方案,有类似的库 帕吉, PAGI

  • AMI——一个文本TCP套接字,工作原理是订阅事件和输入文本命令,从内部类似于SMTP,可以跟踪事件和管理调用,有一个库 聚苯胺 - 最流行的创建与星号的连接

AMI 输出示例

事件:新频道
特权:呼叫,所有
频道:PJSIP/VMS_pjsip-0000078b
频道状态:4
ChannelStateDesc: 响铃
来电号码:111222
来电显示:111222
连接线路编号:
连接线名:
语言:zh
帐户代码:
上下文:来自-pstn
扩展:小号
优先级:1
唯一标识:1599589046.5244
领英:1599589046.5244

  • ARI 是两者的混合体,全部通过 REST、WebSocket,采用 JSON 格式——但有新的库和包装器,不是很好,随手发现(根茎, 帕帕里),这在大约 3 年前就已经在他们的开发中了。

发起呼叫时的 ARI 输出示例

{ "variable":"CallMeCallerIDName", "value":"111222", "type":"ChannelVarset", "timestamp":"2020-09-09T09:38:36.269+0000", "channel":{ "id »:»1599644315.5334″, «名称»:»PJSIP/VMSpjsip-000007b6″, "state":"Ring", "caller":{ "name":"111222″, "number":"111222″ }, "connected":{ "name":"", "number" :"" }, "accountcode":"", "dialplan":{ "context":"from-pstn", "exten":"s", "priority":2, "app名称“:”停滞“,”应用程序data":"hello-world" }, "creationtime":"2020-09-09T09:38:35.926+0000", "language":"en" }, "asteriskid":"48:5b:aa:aa:aa:aa", "application":"hello-world" }

方便或不便,使用特定 API 的可能性或不可能性取决于需要解决的任务。 与 CRM 集成的任务如下:

  • 跟踪呼叫的开始,呼叫转移的位置,提取呼叫者 ID、DID、开始和结束时间,可能还有目录中的数据(以搜索电话和 CRM 用户之间的联系)

  • 开始和结束通话录音,以所需格式保存,在录音结束时告知文件所在的位置

  • 发起对外部事件的呼叫(来自程序),呼叫内部号码,外部号码并连接它们

  • 可选的:与 CRM、拨号器组和 FollowME 集成,以便在没有地方的情况下自动转移呼叫(根据 CRM)

所有这些任务都可以通过 AMI 或 ARI 来解决,但是 ARI 提供的信息要少得多,事件不多,AMI 仍然有很多变量(例如,宏调用,宏内部设置变量,包括调用记录)没有被跟踪。 因此,为了正确和准确的跟踪,我们暂时选择 AMI(但不完全)。 另外(嗯,没有这个哪里来的,我们都是懒人)——原作中(哈布尔文​​章) 使用 PAMI。 *然后你需要尝试重写为 ARI,但不是它会工作的事实。

重塑集成

为了让我们的 FreePBX 能够以简单的方式向 AMI 报告通话的开始、结束时间、号码、录音文件的名称,最简单的方法是使用与原作者相同的技巧来计算通话时长- 输入您的变量并解析其存在的输出。 PAMI 建议通过过滤器功能简单地完成此操作。

下面是为呼叫开始时间设置您自己的变量的示例(s 是在开始 DID 搜索之前执行的拨号方案中的一个特殊数字)

[ext-did-custom]

exten => s,1,Set(CallStart=${STRFTIME(epoch,,%s)})

此行的示例 AMI 事件

事件:新频道

特权:呼叫,所有

频道:PJSIP/VMS_pjsip-0000078b

频道状态:4

ChannelStateDesc: 响铃

来电号码:111222

来电显示:111222

连接线路编号:

连接线名:

语言:zh

帐户代码:

上下文:来自-pstn

扩展:小号

优先级:1

唯一标识:1599589046.5244

领英:1599589046.5244

应用程序:设置 AppData:

通话开始=1599571046

因为 FreePBX 覆盖了 extension.conf 和 extension_ 文件additional.conf,我们将使用该文件 延伸_习俗.conf

extension_custom.conf 的完整代码

[globals]	
;; Проверьте пути и права на папки - юзер asterisk должен иметь права на запись
;; Сюда будет писаться разговоры
WAV=/var/www/html/callme/records/wav 
MP3=/var/www/html/callme/records/mp3

;; По этим путям будет воспроизводится и скачиваться запись
URLRECORDS=https://www.host.ru/callmeplus/records/mp3

;; Адрес для калбека при исходящем вызове
URLPHP=https://www.host.ru/callmeplus

;; Да пишем разговоры
RECORDING=1

;; Это макрос для записи разговоров в нашу папку. 
;; Можно использовать и системную запись, но пока пусть будет эта - 
;; она работает
[recording]
exten => ~~s~~,1,Set(LOCAL(calling)=${ARG1})
exten => ~~s~~,2,Set(LOCAL(called)=${ARG2})
exten => ~~s~~,3,GotoIf($["${RECORDING}" = "1"]?4:14)
exten => ~~s~~,4,Set(fname=${UNIQUEID}-${STRFTIME(${EPOCH},,%Y-%m-%d-%H_%M)}-${calling}-${called})
exten => ~~s~~,5,Set(datedir=${STRFTIME(${EPOCH},,%Y/%m/%d)})
exten => ~~s~~,6,System(mkdir -p ${MP3}/${datedir})
exten => ~~s~~,7,System(mkdir -p ${WAV}/${datedir})
exten => ~~s~~,8,Set(monopt=nice -n 19 /usr/bin/lame -b 32  --silent "${WAV}/${datedir}/${fname}.wav"  "${MP3}/${datedir}/${fname}.mp3" && rm -f "${WAV}/${fname}.wav" && chmod o+r "${MP3}/${datedir}/${fname}.mp3")
exten => ~~s~~,9,Set(FullFname=${URLRECORDS}/${datedir}/${fname}.mp3)
exten => ~~s~~,10,Set(CDR(filename)=${fname}.mp3)
exten => ~~s~~,11,Set(CDR(recordingfile)=${fname}.wav)
exten => ~~s~~,12,Set(CDR(realdst)=${called})
exten => ~~s~~,13,MixMonitor(${WAV}/${datedir}/${fname}.wav,b,${monopt})
exten => ~~s~~,14,NoOp(Finish if_recording_1)
exten => ~~s~~,15,Return()


;; Это основной контекст для начала разговора
[ext-did-custom]

;; Это хулиганство, делать это так и здесь, но работает - добавляем к номеру '8'
exten =>  s,1,Set(CALLERID(num)=8${CALLERID(num)})

;; Тут всякие переменные для скрипта
exten =>  s,n,Gosub(recording,~~s~~,1(${CALLERID(number)},${EXTEN}))
exten =>  s,n,ExecIF(${CallMeCallerIDName}?Set(CALLERID(name)=${CallMeCallerIDName}):NoOp())
exten =>  s,n,Set(CallStart=${STRFTIME(epoch,,%s)})
exten =>  s,n,Set(CallMeDISPOSITION=${CDR(disposition)})

;; Самое главное! Обработчик окончания разговора. 
;; Обычные пути обработки конца через (exten=>h,1,чтототут) в FreePBX не работают - Macro(hangupcall,) все портит. 
;; Поэтому вешаем Hangup_Handler на окончание звонка
exten => s,n,Set(CHANNEL(hangup_handler_push)=sub-call-from-cid-ended,s,1(${CALLERID(num)},${EXTEN}))

;; Обработчик окончания входящего вызова
[sub-call-from-cid-ended]

;; Сообщаем о значениях при конце звонка
exten => s,1,Set(CDR_PROP(disable)=true)
exten => s,n,Set(CallStop=${STRFTIME(epoch,,%s)})
exten => s,n,Set(CallMeDURATION=${MATH(${CallStop}-${CallStart},int)})

;; Статус вызова - Ответ, не ответ...
exten => s,n,Set(CallMeDISPOSITION=${CDR(disposition)})
exten => s,n,Return


;; Обработчик исходящих вызовов - все аналогичено
[outbound-allroutes-custom]

;; Запись
exten => _.,1,Gosub(recording,~~s~~,1(${CALLERID(number)},${EXTEN}))
;; Переменные
exten => _.,n,Set(__CallIntNum=${CALLERID(num)})
exten => _.,n,Set(CallExtNum=${EXTEN})
exten => _.,n,Set(CallStart=${STRFTIME(epoch,,%s)})
exten => _.,n,Set(CallmeCALLID=${SIPCALLID})

;; Вешаем Hangup_Handler на окончание звонка
exten => _.,n,Set(CHANNEL(hangup_handler_push)=sub-call-internal-ended,s,1(${CALLERID(num)},${EXTEN}))

;; Обработчик окончания исходящего вызова
[sub-call-internal-ended]

;; переменные
exten => s,1,Set(CDR_PROP(disable)=true)
exten => s,n,Set(CallStop=${STRFTIME(epoch,,%s)})
exten => s,n,Set(CallMeDURATION=${MATH(${CallStop}-${CallStart},int)})
exten => s,n,Set(CallMeDISPOSITION=${CDR(disposition)})

;; Вызов скрипта, который сообщит о звонке в CRM - это исходящий, 
;; так что по факту окончания
exten => s,n,System(curl -s ${URLPHP}/CallMeOut.php --data action=sendcall2b24 --data ExtNum=${CallExtNum} --data call_id=${SIPCALLID} --data-urlencode FullFname='${FullFname}' --data CallIntNum=${CallIntNum} --data CallDuration=${CallMeDURATION} --data-urlencode CallDisposition='${CallMeDISPOSITION}')
exten => s,n,Return

与原文章作者的原始拨号方案的特点和区别-

  • .conf 格式的拨号方案,正如 FreePBX 所希望的那样(是的,它可以是 .ael,但不是所有版本,而且并不总是很方便)

  • 不是通过 exten=>h 处理结束,而是通过 hangup_handler 引入处理,因为 FreePBX 拨号方案只与它一起工作

  • 固定脚本调用字符串,添加引号和外部调用号码 ExtNum

  • 处理被移动到 _custom contexts 并允许您不触摸或编辑 FreePBX 配置 - 通过 [ext-did-自定义], 通过 [出站-allroutes-自定义]

  • 不绑定数字——文件是通用的,只需要配置路径和链接到服务器

要开始,您还需要通过登录名和密码在 AMI 中运行脚本 - 为此,FreePBX 也有一个 _custom 文件

manager_custom.conf 文件

;;  это логин
[callmeplus]
;; это пароль
secret = trampampamturlala
deny = 0.0.0.0/0.0.0.0

;; я работаю с локальной машиной - но если надо, можно и другие прописать
permit = 127.0.0.1/255.255.255.255
read = system,call,log,verbose,agent,user,config,dtmf,reporting,cdr,dialplan
write = system,call,agent,log,verbose,user,config,command,reporting,originate

这两个文件都必须放在 /etc/asterisk 中,然后重新读取配置(或重新启动 asterisk)

# astrisk -rv
  Connected to Asterisk 16.6.2 currently running on freepbx (pid = 31629)
#freepbx*CLI> dialplan reload
     Dialplan reloaded.
#freepbx*CLI> exit

现在让我们继续学习 PHP

初始化脚本并创建服务

由于使用 AMI 服务 Bitrix 24 的方案并不完全简单和透明,因此应单独讨论。 Asterisk,当 AMI 被激活时,只需打开端口即可。 当客户端加入时,它请求授权,然后客户端订阅必要的事件。 事件以纯文本形式出现,PAMI 将其转换为结构化对象,并提供仅针对感兴趣的事件、字段、数字等设置过滤功能的能力。

一旦调用进入,就会从父 [from-pstn] 上下文开始触发 NewExten 事件,然后所有事件按照上下文中的行顺序进行。 当从 _custom 拨号方案中指定的 CallMeCallerIDName 和 CallStart 变量接收到信息时,

  1. 查询来电分机号码对应的UserID功能。 如果是拨号群怎么办? 问题是政治性的,您是需要一次创建对每个人的呼叫(当每个人同时呼叫时)还是在轮流呼叫时创建呼叫? 大部分client都是Fisrt Available策略,所以这个没有问题,只有一个call。 但问题需要解决。

  2. Bitrix24中的通话注册功能,返回CallID,需要上报通话参数和录音链接。 需要分机号码或用户 ID

了解 FreePBX 并将其与 Bitrix24 等集成

通话结束后,调用录音下载功能,同时上报通话完成状态(Busy、No answer、Success),同时下载带​​有录音的mp3文件链接(如果有的话)。

因为CallMeIn.php模块需要持续运行,所以已经为它创建了一个SystemD启动文件 呼叫我服务,必须放在 /etc/systemd/system/callme.service

[Unit]
Description=CallMe

[Service]
WorkingDirectory=/var/www/html/callmeplus
ExecStart=/usr/bin/php /var/www/html/callmeplus/CallMeIn.php 2>&1 >>/var/log/callmeplus.log
ExecStop=/bin/kill -WINCH ${MAINPID}
KillSignal=SIGKILL

Restart=on-failure
RestartSec=10s

#тут надо смотреть,какие права на папки
#User=www-data  #Ubuntu - debian
#User=nginx #Centos

[Install]
WantedBy=multi-user.target

脚本的初始化和启动通过 systemctl 或服务进行

# systemctl enable callme
# systemctl start callme

该服务将根据需要自行重启(以防崩溃)。 收件箱跟踪服务不需要安装网络服务器,只需要 php(肯定在 FeePBX 服务器上)。 但是在没有通过Web服务器(也有https)访问通话记录的情况下,将无法收听通话记录。

现在我们来谈谈拨出电话。 CallMeOut.php 脚本有两个功能:

  • 当收到 php 脚本请求时发起呼叫(包括使用 Bitrix 本身的“呼叫”按钮)。 没有 web 服务器它不能工作,请求是通过 HTTP POST 接收的,请求包含一个令牌

  • 关于调用的消息、它的参数和 Bitrix 中的记录。 当呼叫结束时,在 [sub-call-internal-ended] 拨号方案中由 Asterisk 触发

了解 FreePBX 并将其与 Bitrix24 等集成

Web 服务器仅用于两件事 - 下载 Bitrix 记录文件(通过 HTTPS)和调用 CallMeOut.php 脚本。 您可以使用内置的FreePBX 服务器,其文件为/var/www/html,您可以安装另一个服务器或指定不同的路径。

网络服务器

让我们将网络服务器设置留给独立研究(tyts, tyts, tyts). 如果你没有域,你可以试试 FreeDomain( https://www.freenom.com/ru/index.html),这将为您的白色 IP 提供一个免费名称(如果外部地址仅在路由器上,请不要忘记通过路由器转发端口 80、443)。 如果您刚刚创建了一个 DNS 域,那么您必须等待(从 15 分钟到 48 小时)直到所有服务器都加载完毕。 根据与国内供应商合作的经验 - 从 1 小时到一天。

安装自动化

github 上开发了一个安装程序,使安装更加容易。 但它在纸面上很顺利 - 当我们手动安装它时,因为在选择所有这些之后,它变得非常清楚什么是朋友,谁去哪里以及如何调试它。 还没有安装程序

码头工人

如果你想快速尝试解决方案——Docker 有一个选项——快速创建一个容器,给它外部端口,滑动设置文件并尝试(这是 LetsEncrypt 容器的选项,如果你已经有证书,你只需要将反向代理重定向到FreePBX web服务器(我们给它另一个端口是88),LetsEncrypt in docker based on 这篇文章

您需要在下载的项目文件夹中运行该文件(在 git clone 之后),但首先进入星号配置(星号文件夹)并在那里写入记录的路径和您的站点的 URL

version: '3.3'
services:
  nginx:
    image: nginx:1.15-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/ssl_docker.conf:/etc/nginx/conf.d/ssl_docker.conf
  certbot:
    image: certbot/certbot
  freepbx:
    image: flaviostutz/freepbx
    ports:
      - 88:80 # для настройки
      - 5060:5060/udp
      - 5160:5160/udp
      - 127.0.0.1:5038:5038 # для CallMeOut.php
#      - 3306:3306
      - 18000-18100:18000-18100/udp
    restart: always
    environment:
      - ADMIN_PASSWORD=admin123
    volumes:
      - backup:/backup
      - recordings:/var/spool/asterisk/monitor
      - ./callme:/var/www/html/callme
      - ./systemd/callme.service:/etc/systemd/system/callme.conf
      - ./asterisk/manager_custom.conf:/etc/asterisk/manager_custom.conf
      - ./asterisk/extensions_custom.conf:/etc/asterisk/extensions_custom.conf
#      - ./conf/startup.sh:/startup.sh

volumes:
  backup:
  recordings:

这个 docker-compose.yaml 文件是通过

docker-compose up -d

如果nginx没有启动,那么nginx/ssl_docker.conf文件夹中的配置有问题

其他集成

我们想,为什么不同时将一些 CRM 放入脚本中呢? 我们研究了其他几种 CRM API,尤其是免费的内置 PBX - ShugarCRM 和 Vtiger,是的! 是的,原理是一样的。 不过这是另外一回事了,我们稍后会单独上传到github。

引用

免责声明:任何与现实的相似之处都是虚构的,那不是我。

来源: habr.com

添加评论