Nous traitons FreePBX et l'intégrons à Bitrix24 et pas seulement

Bitrix24 est un énorme moissonneur qui combine le CRM, le flux de documents, la comptabilité et bien d'autres choses que les gestionnaires aiment vraiment et que le personnel informatique n'aime pas vraiment. Le portail est utilisé par de nombreuses petites et moyennes entreprises, y compris de petites cliniques, des travailleurs de la fabrication et même des salons de beauté. La fonction principale que les managers «adorent» est l'intégration de la téléphonie et du CRM, lorsqu'un appel est immédiatement enregistré dans le CRM, des cartes client sont créées, des informations sur le client sont affichées à l'entrée, et vous pouvez voir immédiatement qui il est, ce qu'il peut vendre et combien il doit. Mais la téléphonie de Bitrix24 et son intégration avec le CRM coûte de l'argent, parfois beaucoup. Dans l'article, je vais vous raconter l'expérience de l'intégration avec des outils ouverts et le populaire IP PBX FreePBX , et également considérer la logique du travail de diverses parties

Je travaille comme entreprise d'externalisation dans une entreprise qui vend et configure, intègre la téléphonie IP. Quand on m'a demandé si nous pouvions offrir ceci et cette société quelque chose pour intégrer Bitrix24 avec les PBX que les clients ont, ainsi qu'avec les PBX virtuels sur diverses sociétés VDS, je suis allé chez Google. Et il, bien sûr, m'a donné un lien vers un article dans Habr , où il y a une description, et github, et tout semble fonctionner. Mais en essayant d'utiliser cette solution, il s'est avéré que Bitrix24 n'est plus ce qu'il était et que beaucoup de choses doivent être refaites. De plus, FreePBX n'est pas un astérisque nu pour vous, vous devez ici réfléchir à la façon de combiner la convivialité et un plan de numérotation hardcore dans les fichiers de configuration.

Nous étudions la logique du travail

Alors d'abord, comment est-il censé fonctionner. Lorsqu'un appel arrive de l'extérieur vers le PBX (événement SIP INVITE du fournisseur), le traitement du plan de numérotation (plan de numérotation, plan de numérotation) commence - les règles de ce qu'il faut faire avec l'appel et dans quel ordre. De nombreuses informations peuvent être obtenues à partir du premier package, qui peuvent ensuite être utilisées dans les règles. Un excellent outil pour étudier l'intérieur de SIP est l'analyseur sngrep ( lien ), qui est simplement installé dans les distributions populaires via apt install / yum install et autres, mais vous pouvez également le construire à partir des sources. Voyons le journal des appels dans sngrep

Dans une forme simplifiée, le plan de numérotation ne traite que du premier paquet, parfois pendant la conversation, les appels sont transférés, les boutons enfoncés (DTMF), diverses choses intéressantes comme FollowMe, RingGroup, IVR et autres.

Contenu du package Invite

DID CallerID. DID - , CallerID - .

- (/ ) (Ring Group), IVR (, ... ...), (Phrases), (Time Conditions), (FollowMe, Forward). , .

"". Asterisk - , ( exten, exten=DID). - ( - Dial(), - Hangup()), (IF, ELSE, ExecIF ), (Goto, GotoIF), (Gosub, Macro). include _, . , include .

FreePBX include Gosub, Macro Handler. FreePBX

, (Macro), (Gosub) (Goto), , .

. DID, , - . 1 . hangupcall, , (hangup handler).

CRM, , CRM?

CRM? , . API, API HTTP REST. asterisk.

Asterisk :

AMI

Event: Newchannel Privilege: call,all Channel: PJSIP/VMS_pjsip-0000078b ChannelState: 4 ChannelStateDesc: Ring CallerIDNum: 111222 CallerIDName: 111222 ConnectedLineNum: ConnectedLineName: Language: en AccountCode: Context: from-pstn Exten: s Priority: 1 Uniqueid: 1599589046.5244 Linkedid: 1599589046.5244

ARI

{ "variable":"CallMeCallerIDName", "value":"111222", "type":"ChannelVarset", "timestamp":"2020-09-09T09:38:36.269+0000", "channel":{ "id":"1599644315.5334", "name":"PJSIP/VMSpjsip-000007b6", "state":"Ring", "caller":{ "name":"111222", "number":"111222" }, "connected":{ "name":"", "number":"" }, "accountcode":"", "dialplan":{ "context":"from-pstn", "exten":"s", "priority":2, "appname":"Stasis", "appdata":"hello-world" }, "creationtime":"2020-09-09T09:38:35.926+0000", "language":"ru" }, "asteriskid":"48:5b:aa:aa:aa:aa", "application":"hello-world" }

, API , . CRM :

  • , , CallerID, 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

Event: Newchannel

Privilege: call,all

Channel: PJSIP/VMS_pjsip-0000078b

ChannelState: 4

ChannelStateDesc: Ring

CallerIDNum: 111222

CallerIDName: 111222

ConnectedLineNum:

ConnectedLineName:

Language: en

AccountCode:

Context: from-pstn

Exten: s

Priority: 1

Uniqueid: 1599589046.5244

Linkedid: 1599589046.5244

Application: Set AppData:

CallStart=1599571046

FreePBX extention.conf extention_additional.conf, extention_custom.conf

extention_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 FreePBX - [ext-did-custom], [outbound-allroutes-custom]

  • -

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, ( )

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

PHP

24, AMI , . AMI . , . , PAMI , , ..

, NewExten [from-pstn], . _custom CallMeCallerIDName CallStart

  1. UserID, , . ? , ( ) ? Fisrt Available, , .

  2. 24, CallID, . UserID

, (, , ), mp3 ( ).

CallMeIn.php , SystemD callme.service, /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 service

# systemctl enable callme
# systemctl start callme

( ). , php ( FeePBX). ( https) .

. CallMeOut.php :

  • php ( "" ). , HTTP POST,

  • , . Asterisk [sub-call-internal-ended]

- ( HTTPS) CallMeOut.php. FreePBX, /var/www/html, .

(, , ). , FreeDomain( https://www.freenom.com/ru/index.html), IP ( 80, 443 , ). DNS , ( 15 48 ) . - 1 .

github , . - , , , . (

Docker

- Docker - , , ( LetsEncrypt , , FreePBX ( - 88), LetsEncrypt

( git clone), ( asterisk) 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 , . API CRM, - ShugarCRM Vtiger, ! , . , .

: ,




All Articles