Asterisk Integration with amoCRM, step-by-step guide
- Tutorial
On the network you can find instructions of varying degrees of limitation and completeness of the information presented on the topic in the heading of the article, but even putting them all together, you will need straight arms, a file and some patience to achieve the desired catharsis.
Here I will present my experience of connecting Asterisk to amoCRM in the form of step-by-step instructions, highlighting all the necessary nuances, from receiving an ssl certificate, setting up a web server and ending with a demonstration of the resulting bundle.
* for the impatient, what will result from the manipulations performed, see the
end of the article
On our test bench are installed:
In this guide, we will use a free certificate from Let's Encrypt.
Initially, I planned to use StartSSL and wrote a step-by-step instruction for obtaining certificates there, but only after I noticed that no browser accepts their root certificates.
The procedure for obtaining it is quite trivial, but I will nevertheless describe it in steps.
As rightly noted in the comments, the lifetime of the certificates received is 3 months and they will need to be updated. Take this into account!
As mentioned in the introduction, we will use the NGINX web server.
I will not breed hollywar'ov and somehow motivate my choice, just - we have NGINX and we will configure it.
The basis of the config was a wonderful article by DimaSmirnov “Nginx and https. We get class A + ” , for which I take this opportunity to express my gratitude.
So, the web server configuration file has the following form (some comments are given directly in the config):
In the folder / var / www / asterisk / (in my case), you need to create a symlink to the folder where the conversation recording files will be stored (I will tell about setting up conversation records below)
A few more words about certificates. In addition to vistep.ru.key and vistep.ru.pem already in place, we will also need dhparam.pem.
For sim with the NGINX configuration, finish and move on to setting up Asterisk.
In order for amoCRM to communicate with our Asterisk, manager.conf and http.conf need to be converted to:
Restart Asterisk and check if everything has risen as we need
Dialplan example (I use ael, but I'm sure that anyone can translate to lua or conf if desired):
Important!
In the incoming context (as I called the context where I process incoming calls), in a single extension, there is such a line:
This command allows us to display the names of the calling customers on the phones of employees, picking them up from amoCRM.
Let's analyze the link from this command into components:
Team Work Example
Now about the special extension 100500. Let me remind you that in dialplan it looks
The link for wget is almost identical and the rules described above apply to it. And he is needed for the so-called. “Smart call forwarding”, when an incoming call is forwarded by an employee to 100500, and then Asterisk and amoCRM decide for themselves to whom it should be sent (read send to the responsible manager or “default” manager).
Why is this useful, you ask? Imagine a typical office situation: In conjunction with amoCRM, it will look like this: thanks for the information, thanks to the guys from voxlink - voxlink.ru/kb/integraciya-s-crm/amocrm-asterisk And yes, I completely forgot if your Asterisk is not configured yet maintaining a database in MySQL, then in this article you will find all the necessary instructions.
Also, do not forget to add another field to the CDR label (you need to be able to listen to conversations in the client card in amoCRM)
and execute a couple more commands
At this point, we are waiting for the largest number of rakes, so be careful.
First of all, connect Asterisk in the amoCRM interface.
To do this, go to “Settings” → “Integrations” → find Asterisk there and click “Install”.
We will be presented with a description of integration and a certain number of links to guides, all of this can be safely scrolled down to the bottom to the information input fields.
Login - amocrm (from manager.conf)
Password - JD3clEB8f4-_3ry84gJ (from manager.conf)
The path to the script is _https: //asterisk.vistep.ru/amocrm.php
As well as internal numbers of your company employees.
The next step is to configure the amocrm.php script.
It can be downloaded from the link in the integration description, but I want to pay attention that the information laid out here has been fixed for a specific dialplan, or rather, the specific context of dial_out calls originalization (line 99), in order to correspond to the Asterisk settings on the stand. Keep this in mind and change to your context if it is different (this is necessary to make calls in a couple of clicks directly from amoCRM).
Note!
My explanations for the parameters at the beginning of the script are given directly in the code.
You can check the script operation using the following links (note - I use my login / password and the path to the script, they must be different for you):
_https: //asterisk.vistep.ru/amocrm.php? _Login = amocrm & _secret = JD3clEB8f4-_3ry84gJ & _action = test_cdr
_https: //asterisk.vistep.ru/amocrm.php? _login = amocrm & _secret = JD3clEB8f4-_3ry84gJ & _action = status the
exhaust should be like on
Based on the results of the settings, we get the following features:
The video format is best for demonstration, so please:
I hope in this article I managed to completely close the issue of integrating amoCRM and Asterisk.
If you have any questions, welcome in the comments.
There is no account on Habré? - My coordinates are a profile, write, I will try to help.
Asterisk is fun!
Good luck to all!
Here I will present my experience of connecting Asterisk to amoCRM in the form of step-by-step instructions, highlighting all the necessary nuances, from receiving an ssl certificate, setting up a web server and ending with a demonstration of the resulting bundle.
* for the impatient, what will result from the manipulations performed, see the
end of the article
Introductory
On our test bench are installed:
- Debian OS
lsb_release -a No LSB modules are available. Distributor ID: Debian Description: Debian GNU/Linux 8.7 (jessie) Release: 8.7 Codename: jessie
- IP PBX Asterisk
*CLI> core show version Asterisk 13.14.0 built by root @ asterisk.vistep.ru on a x86_64 running Linux on 2017-03-29 05:47:19 UTC
- NGINX web server
sudo nginx -v nginx version: nginx/1.10.3
- PHP-FPM
php5-fpm -v PHP 5.6.30-0+deb8u1 (fpm-fcgi) (built: Feb 8 2017 08:51:18) Copyright (c) 1997-2016 The PHP Group Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2016, by Zend Technologies
- Domain name for the test
tawny-owl:~$ dig +short asterisk.vistep.ru 138.201.164.52
We receive the ssl certificate
In this guide, we will use a free certificate from Let's Encrypt.
Initially, I planned to use StartSSL and wrote a step-by-step instruction for obtaining certificates there, but only after I noticed that no browser accepts their root certificates.
The procedure for obtaining it is quite trivial, but I will nevertheless describe it in steps.
- Go to letsencrypt.org and click "Get Started"screen
- Next, we are interested in the section With Shell Access, in which we find all the necessary instructionsscreen
- Go to certbot.eff.org and choose our softwarescreen
- Then we follow the instructions and executeseveral teams in cosnoli
echo"deb http://ftp.debian.org/debian jessie-backports main" >> /etc/apt/sources.list apt-get update apt-get install certbot -t jessie-backports
- Then you need to send a certificate request using the certbot utility.
I went the most primitive way:
drove a teamcertbot certonly
and followed the steps of the wizard, where he indicated his email, the path to webroot, domain name, etc.screenshots - At the exit we see treasured
IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at /etc/letsencrypt/live/asterisk.vistep.ru/fullchain.pem. Your cert will expire on 2017-06-27. To obtain a new or tweaked version of this certificate in the future, simply run certbot again. To non-interactively renew *all* of your certificates, run "certbot renew" - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le
- Copy certificates to their locationsHidden text
cp /etc/letsencrypt/live/asterisk.vistep.ru/privkey.pem /etc/nginx/certs/vistep.ru.key cp /etc/letsencrypt/live/asterisk.vistep.ru/fullchain.pem /etc/nginx/certs/vistep.ru.pem
As rightly noted in the comments, the lifetime of the certificates received is 3 months and they will need to be updated. Take this into account!
Web server setup
As mentioned in the introduction, we will use the NGINX web server.
I will not breed hollywar'ov and somehow motivate my choice, just - we have NGINX and we will configure it.
The basis of the config was a wonderful article by DimaSmirnov “Nginx and https. We get class A + ” , for which I take this opportunity to express my gratitude.
So, the web server configuration file has the following form (some comments are given directly in the config):
/etc/nginx/conf.d/asterisk.vistep.ru.conf
server {
server_name asterisk.vistep.ru;
listen138.201.164.52:80;
rewrite ^ https://asterisk.vistep.ru$request_uri? permanent;
}
server {
access_log /var/log/nginx/asterisk.vistep.ru.access.log;
error_log /var/log/nginx/asterisk.vistep.ru.error.log;
listen443 ssl;
server_name asterisk.vistep.ru;
resolver8.8.8.8;
ssl_staplingon;
sslon;
ssl_certificate /etc/nginx/certs/vistep.ru.pem;
ssl_certificate_key /etc/nginx/certs/vistep.ru.key;
ssl_dhparam /etc/nginx/certs/dhparam.pem;
ssl_session_timeout24h;
ssl_session_cache shared:SSL:2m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers kEECDH+AES128:kEECDH:kEDH:-3DES:kRSA+AES128:kEDH+3DES:DES-CBC3-SHA:!RC4:!aNULL:!eNULL:!MD5:!EXPORT:!LOW:!SEED:!CAMELLIA:!IDEA:!PSK:!SRP:!SSLv2;
ssl_prefer_server_cipherson;
add_header Strict-Transport-Security "max-age=31536000;";
add_header Content-Security-Policy-Report-Only "default-src https:; script-src https: 'unsafe-eval' 'unsafe-inline'; style-src https: 'unsafe-inline'; img-src https: data:; font-src https: data:; report-uri /csp-report";
root /var/www/asterisk;
index index.php index.html index.htm index.nginx-debian.html;
location records/ {
autoindexoff;
allow89.108.120.223;
allow89.108.122.9;
allow95.213.171.78;
allow95.213.156.46;
allow209.160.27.20;
allow89.189.163.20; # адреса выше - адреса amoCRM и они нужны, а этот - мой домашний, не нужно его вставлять в конфиг ;) актуальный список адресов - https://www.amocrm.ru/security/iplist.txtdeny all;
}
location / {
try_files$uri$uri/ =404;
allow89.108.120.223;
allow89.108.122.9;
allow95.213.171.78;
allow95.213.156.46;
allow209.160.27.20;
allow89.189.163.20; # адреса выше - адреса amoCRM и они нужны, а этот - мой домашний, не нужно его вставлять в конфиг ;) актуальный список адресов - https://www.amocrm.ru/security/iplist.txtdeny all;
}
location~ \.php$ {
allow89.108.120.223;
allow89.108.122.9;
allow95.213.171.78;
allow95.213.156.46;
allow209.160.27.20;
allow89.189.163.20; # адреса выше - адреса amoCRM и они нужны, а этот - мой домашний, не нужно его вставлять в конфиг ;) актуальный список адресов - https://www.amocrm.ru/security/iplist.txtdeny all;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_buffers1616k;
fastcgi_buffer_size32k;
}
}
In the folder / var / www / asterisk / (in my case), you need to create a symlink to the folder where the conversation recording files will be stored (I will tell about setting up conversation records below)
Hidden text
cd /var/www/asterisk/
ln -s /var/calls/ records
ln -s /var/calls/ records
A few more words about certificates. In addition to vistep.ru.key and vistep.ru.pem already in place, we will also need dhparam.pem.
create it
openssl dhparam -out /etc/nginx/certs/dhparam.pem 4096
For sim with the NGINX configuration, finish and move on to setting up Asterisk.
Configure IP PBX Asterisk
In order for amoCRM to communicate with our Asterisk, manager.conf and http.conf need to be converted to:
manager.conf
[general]
enabled = yes
port = 5038
bindaddr = 0.0.0.0
webenabled = yes
httptimeout = 60
debug = on
[amocrm]
secret = JD3clEB8f4-_3ry84gJ
deny = 0.0.0.0/0.0.0.0
permit = 127.0.0.1/255.255.255.0
read = cdr,reporting,originate
write = reporting,originate
http.conf
[general]
enabled=yes
enablestatic=yes
bindaddr=0.0.0.0
bindport=8088
prefix=asterisk
Restart Asterisk and check if everything has risen as we need
exhaust
asterisk*CLI> http show status
HTTP Server Status:
Prefix: /asterisk
Server: Asterisk/13.14.0
Server Enabled and Bound to 0.0.0.0:8088
Enabled URI's:
/asterisk/httpstatus => Asterisk HTTP General Status
/asterisk/phoneprov/... => Asterisk HTTP Phone Provisioning Tool
/asterisk/amanager => HTML Manager Event Interface w/Digest authentication
/asterisk/arawman => Raw HTTP Manager Event Interface w/Digest authentication
/asterisk/manager => HTML Manager Event Interface
/asterisk/rawman => Raw HTTP Manager Event Interface
/asterisk/static/... => Asterisk HTTP Static Delivery
/asterisk/amxml => XML Manager Event Interface w/Digest authentication
/asterisk/mxml => XML Manager Event Interface
/asterisk/ari/... => Asterisk RESTful API
/asterisk/ws => Asterisk HTTP WebSocket
Enabled Redirects:
None.
asterisk*CLI> manager show settings
Global Settings:
----------------
Manager (AMI): Yes
Web Manager (AMI/HTTP): Yes
TCP Bindaddress: 0.0.0.0:5038
HTTP Timeout (minutes): 60
TLS Enable: No
TLS Bindaddress: Disabled
TLS Certfile: asterisk.pem
TLS Privatekey:
TLS Cipher:
Allow multiple login: Yes
Display connects: Yes
Timestamp events: No
Channel vars:
Debug: Yes
Dialplan example (I use ael, but I'm sure that anyone can translate to lua or conf if desired):
extensions.ael
globals {
WAV=/var/calls; //Временный каталог с WAV
MP3=/var/calls; //Куда выгружать mp3 файлы
RECORDING=1; // Запись, 1 - включена.
};
macro recording (calling,called) {
if ("${RECORDING}" = "1"){
Set(fname=${UNIQUEID}-${STRFTIME(${EPOCH},,%Y-%m-%d-%H_%M)}-${calling}-${called});
Set(datedir=${STRFTIME(${EPOCH},,%Y/%m/%d)});
System(mkdir -p ${WAV}/${datedir});
Set(monopt=nice -n 19 /usr/bin/lame -b 32 --silent "${WAV}/${datedir}/${fname}.wav""${MP3}/${datedir}/${fname}.mp3" && chmod o+r "${MP3}/${datedir}/${fname}.*");
Set(CDR(filename)=${fname}.mp3);
Set(CDR(recordingfile)=${fname}.wav);
Set(CDR(realdst)=${called});
MixMonitor(${WAV}/${datedir}/${fname}.wav,b,${monopt});
};
};
context dial_out {
// звоним друг другу
_[71]XX => {
&recording(${CALLERID(number)},${EXTEN});
Dial(SIP/${EXTEN},,tTr);
Hangup();
}
// кому позвонить решит amoCRM!
100500 => {
Set(DEFMAN=123); // по умолчанию звоним на 123
Set(TOEXT=${SHELL(wget -O - --quiet "https://vistepru.amocrm.ru/private/acceptors/asterisk_new/?redirect=Y&number=${CALLERID(num)}&USER_LOGIN=ceo@vistep.ru&USER_HASH=1dc1444b0d3172c1113ffea9078c575c")}); // получаем номер ответственного менеджера
Dial(SIP/${TOEXT},,tTr); // звоним ответственному менеджеру // если он не отвечает или ошибка, звоним на номер по умолчанию
if ("${DIALSTSTUS}" != "ANSWERED") {
Dial(SIP/${DEFMAN},,tTr);
}
HangUP();
} // end 100500
_XXXXXX => {
NoOP(=== CALL FROM ${CALLERID(number)} TO ${EXTEN} ===);
&recording(${CALLERID(number)},${EXTEN});
Dial(SIP/83843${EXTEN}@multifon,180,tT);
HangUP();
} // end of _XXXXXX
_[78]XXXXXXXXXX => {
NoOP(=== CALL TO ${EXTEN} ===);
&recording(${CALLERID(number)},${EXTEN});
Dial(SIP/${EXTEN}@multifon,180,tT);
HangUP();
}// end of _[78]XXXXXXXXXX
_+7XXXXXXXXXX => {
NoOP(=== CALL TO ${EXTEN} ===);
&recording(${CALLERID(number)},${EXTEN});
Dial(SIP/${EXTEN}@multifon,180,tT);
HangUP();
}// end of _+7XXXXXXXXXX
//все остальные звонки, не прописанные выше, идут в лес
_X. => {
Hangup();
}
}
context default {
// в контексте по умолчанию все отправляется лесом
_X. => {
Hangup();
}
};
context incoming {
_[87]XXXXXXXXXX => {
&recording(${CALLERID(number)},${EXTEN});
Answer();
Set(CHANNEL(musicclass)=vistep.ru);
Set(CUSTOMER_NAME=${SHELL(wget -O - --quiet "https://vistepru.amocrm.ru/private/acceptors/asterisk_new/?number=${CALLERID(num)}&USER_LOGIN=ceo@vistep.ru&USER_HASH=1dc1444b0d3172c1113ffea9078c575c"|cut -d "|" -f1)});
Set(CALLERID(name)=${CUSTOMER_NAME});
Queue(queue_1,tT);
NoOp(=== ${HANGUPCAUSE} ===);
HangUP();
}
}
Important!
In the incoming context (as I called the context where I process incoming calls), in a single extension, there is such a line:
Set(CUSTOMER_NAME=${SHELL(wget -O - --quiet "https://vistepru.amocrm.ru/private/acceptors/asterisk_new/?number=${CALLERID(num)}&USER_LOGIN=ceo@vistep.ru&USER_HASH=1dc1444b0d3172c1113ffea9078c575c"|cut -d "|" -f1)});
This command allows us to display the names of the calling customers on the phones of employees, picking them up from amoCRM.
Let's analyze the link from this command into components:
- vistepru.amocrm.ru/private/acceptors/asterisk_new ? where instead of vistepru you should have your subdomain in amocrm registered
- USER_LOGIN=ceo@vistep.ru where instead of my email should be your (admin)
- USER_HASH = 1dc1444b0d3172c1119593ffea9078c575c where instead of my API key (in the amoCRM interface "Settings" → "API) specify your API key
Team Work Example
Hidden text
Now about the special extension 100500. Let me remind you that in dialplan it looks
So
// кому позвонить решит amoCRM!
100500 => {
Set(DEFMAN=123); // по умолчанию звоним на 123
Set(TOEXT=${SHELL(wget -O - --quiet "https://vistepru.amocrm.ru/private/acceptors/asterisk_new/?redirect=Y&number=${CALLERID(num)}&USER_LOGIN=ceo@vistep.ru&USER_HASH=1dc1444b0d3172c1113ffea9078c575c")}); // получаем номер ответственного менеджера
Dial(SIP/${TOEXT},,tTr); // звоним ответственному менеджеру // если он не отвечает или ошибка, звоним на номер по умолчанию
if ("${DIALSTSTUS}" != "ANSWERED") {
Dial(SIP/${DEFMAN},,tTr);
}
HangUP();
} // end 100500
The link for wget is almost identical and the rules described above apply to it. And he is needed for the so-called. “Smart call forwarding”, when an incoming call is forwarded by an employee to 100500, and then Asterisk and amoCRM decide for themselves to whom it should be sent (read send to the responsible manager or “default” manager).
Why is this useful, you ask? Imagine a typical office situation: In conjunction with amoCRM, it will look like this: thanks for the information, thanks to the guys from voxlink - voxlink.ru/kb/integraciya-s-crm/amocrm-asterisk And yes, I completely forgot if your Asterisk is not configured yet maintaining a database in MySQL, then in this article you will find all the necessary instructions.
- Входящий звонок от ООО "Шубы Саурона"
- Звонок принимает менеджер Боромир, понимает, что это не его клиент и начинает кричать в рупор на весь офис: - Чей клиент "Шубы Сарумана"? (еще и ошибается в добавок!)
- Галадриель из конца кабинета кричит, что ее
- Боромир спрашивает какой у нее внутренний номер и только затем переводит вызов.
- Входящий звонок от ООО "Шубы Саурона"
- Звонок принимает менеджер Боромир, понимает, что это не его клиент и переводит вызов на 100500
- Asterisk и amoCRM путем не сложной магии сами решают, что вызов нужно отправить Галадриель
- PROFIT!
Also, do not forget to add another field to the CDR label (you need to be able to listen to conversations in the client card in amoCRM)
Hidden text
ALTER TABLE `cdr` ADD `recordingfile` VARCHAR (120) NOT NULL
and execute a couple more commands
Spoiler heading
echo"alias recordingfile => recordingfile" >> cdr_mysql.conf
asterisk -rx 'core restart now'
AmoCRM setup
At this point, we are waiting for the largest number of rakes, so be careful.
First of all, connect Asterisk in the amoCRM interface.
To do this, go to “Settings” → “Integrations” → find Asterisk there and click “Install”.
We will be presented with a description of integration and a certain number of links to guides, all of this can be safely scrolled down to the bottom to the information input fields.
Login - amocrm (from manager.conf)
Password - JD3clEB8f4-_3ry84gJ (from manager.conf)
The path to the script is _https: //asterisk.vistep.ru/amocrm.php
As well as internal numbers of your company employees.
screen
The next step is to configure the amocrm.php script.
It can be downloaded from the link in the integration description, but I want to pay attention that the information laid out here has been fixed for a specific dialplan, or rather, the specific context of dial_out calls originalization (line 99), in order to correspond to the Asterisk settings on the stand. Keep this in mind and change to your context if it is different (this is necessary to make calls in a couple of clicks directly from amoCRM).
amocrm.php
<?php/*
amoCRM to asterisk integration.
QSOFT LLC, All rights reserved.
mailto: support@amocrm.com.
Date: 10.04.2012 rev: 102703
Cannot be redistributed without
a written permission.
_____ _____ __ __
/ ____| __ \| \/ |
__ _ _ __ ___ ___ | | | |__) | \ / |
/ _` | '_ ` _ \ / _ \| | | _ /| |\/| |
| (_| | | | | | | (_) | |____| | \ \| | | |_
\__,_|_| |_| |_|\___/ \_____|_| \_\_| |_(_)
*/
ini_set('log_errors','On');
ini_set('error_log', '/var/log/php_errors.log');
define('AC_HOST','localhost'); // где слушает AMI/AJAM
define('AC_PORT',8088); // какой порт слушает (у нас 8088) см. http.conf Asterisk'а
define('AC_PREFIX','/asterisk/'); // см. http.conf Asterisk'а
define('AC_TLS',false);
define('AC_DB_CS','mysql:host=localhost;port=3306;dbname=asterisk'); //хост, где крутится MySQL с БД Asterisk'а, порт и имя БД
define('AC_DB_UNAME','asterisk_user'); //каким юзером цепляться к БД
define('AC_DB_UPASS','232wwQd293f_2edxse3e'); //пароль этого юзера
define('AC_TIMEOUT',0.75);
define('AC_RECORD_PATH','https://asterisk.vistep.ru/records/%Y/%m/%d/#'); //путь, по которому забирать файлы записей разговоров
define('AC_TIME_DELTA',7); // hours. Ex. GMT+4 = 4
$db_cs=AC_DB_CS;
$db_u=!strlen(AC_DB_UNAME)?NULL:AC_DB_UNAME;
$db_p=!strlen(AC_DB_UPASS)?NULL:AC_DB_UPASS;
date_default_timezone_set('UTC');
if (AC_PORT<1) die('Please, configure settings first!'); // die if notif (defined('AC_RECORD_PATH') AND !empty($_GET['GETFILE'])){
//get file. Do not check auth. (uniqueid is rather unique)
$p=AC_RECORD_PATH;
if (empty($p)) die('Error while getting file from asterisk');
try {
$dbh = new PDO($db_cs, $db_u, $db_p);
$sth = $dbh->prepare('SELECT calldate,recordingfile FROM cdr WHERE uniqueid= :uid');
$sth->bindValue(':uid',strval($_GET['GETFILE']));
$sth->execute();
$r = $sth->fetch(PDO::FETCH_ASSOC);
if ($r===falseORempty($r['recordingfile'])) die('Error while getting file from asterisk');
$date=strtotime($r['calldate']);
$replace=array();
$replace['#']=$r['recordingfile'];
$dates=array('d','m','Y','y');
foreach ($dates as $d) $replace['%'.$d]=date($d,$date); // not a good idea!
$p=str_replace(array_keys($replace),array_values($replace),$p);
if (empty($_GET['noredirect'])) header('Location: '.$p);
die($p);
} catch (PDOException $e) {
die('Error while getting file from asterisk');
}
}
// filter parameters from _GETforeach (array('login','secret','action') as $k){
if (empty($_GET['_'.$k])) die('NO_PARAMS');
$$k=strval($_GET['_'.$k]);
}
// trying to check accacess
$loginArr=array(
'Action'=>'Login',
'username'=>$login,
'secret'=>$secret,
// 'Events'=>'off',
);
$resp=asterisk_req($loginArr,true);
// problems? exitingif ($resp[0]['response']!=='Success') answer(array('status'=>'error','data'=>$resp[0]));
//auth OK. Lets perform actionsif ($action==='status'){ // list channels status
$params=array( 'action'=>'status');
$resp=asterisk_req($params);
// report error of anyif ($resp[0]['response']!=='Success') answer(array('status'=>'error','data'=>$resp[0]));
// first an last chunks are uselessunset($resp[end(array_keys($resp))],$resp[0]);
// renumber keys for JSON
$resp=array_values($resp);
// report OK
answer(array('status'=>'ok','action'=>$action,'data'=>$resp));
}elseif ($action==='call'){ // originate a call
$params=array(
'action'=>'Originate',
'channel'=>'SIP/'.intval($_GET['from']),
'Exten'=>strval($_GET['to']),
'Context'=>'dial_out', //was from-internal'priority'=>'2',
'Callerid'=>'"'.strval($_GET['as']).'" <'.intval($_GET['from']).'>',
'Async'=>'Yes',
// Not Implemented://'Callernumber'=>'150',//'CallerIDName'=>'155',
);
$resp=asterisk_req($params,true);
if ($resp[0]['response']!=='Success') answer(array('status'=>'error','data'=>$resp[0]));
answer(array('status'=>'ok','action'=>$action,'data'=>$resp[0]));
} elseif ($action==='test_cdr'){ // test if DB connection params are OK.if (!class_exists('PDO')) answer(array('status'=>'error','data'=>'PDO_NOT_INSTALLED')); // we use PDO for accessing mySQL pgSQL sqlite within same algorythmtry {
$dbh = new PDO($db_cs, $db_u, $db_p);
} catch (PDOException $e) {
answer(array('status'=>'error','data'=>$e->getMessage()));
}
answer(array('status'=>'ok','data'=>'connection ok'));
} elseif ($action==='cdr'){ // fetch call historytry {
$dbh = new PDO($db_cs, $db_u, $db_p);
foreach (array('date_from','date_to') as $k){
$v=doubleval( (!empty($_GET[$k]))?intval($_GET[$k]):0 );
if ($v<0) $v=time()-$v;
$$k=$v;
}
if ($date_from<time()-10*24*3600) $date_from=time()-7*24*3600; //retr. not more than 10d before
$date_from=($date_from?$date_from+AC_TIME_DELTA*3600:0); //default 01-01-1970
$date_to =($date_to ?$date_to +AC_TIME_DELTA*3600:time()+AC_TIME_DELTA*3600);//default now()
$sth = $dbh->prepare('SELECT calldate, src,dst,duration,billsec,uniqueid,recordingfile FROM cdr WHERE disposition=\'ANSWERED\' AND billsec>=:minsec AND calldate> :from AND calldate< :to');
// BETWEEN is illegal on some bcknds
header("X-REAL_DATE:" . gmdate('Y-m-d H:i:s',$date_from).'@'. gmdate('Y-m-d H:i:s',$date_to));
$sth->bindValue(':from', date('Y-m-d H:i:s',$date_from) );
$sth->bindValue(':to', date('Y-m-d H:i:s',$date_to));
$sth->bindValue(':minsec',!empty($_GET['minsec'])?$_GET['minsec']:5,PDO::PARAM_INT);
$sth->execute();
//$sth->debugDumpParams(); var_dump($sth->errorInfo());
$r = $sth->fetchAll(PDO::FETCH_ASSOC);
foreach ($r as $k=>$v) $r[$k]['calldate']=date('Y-m-d H:i:s',strtotime($v['calldate'])-AC_TIME_DELTA*3600);
answer(array('status'=>'ok','data'=>$r),true);
} catch (PDOException $e) {
answer(array('status'=>'error','data'=>$e->getMessage()),true);
}
} elseif ($action==='pop'){// fill test data. Maybe you will need it. Just comment line below.die();
$dbh = new PDO($db_cs, $db_u, $db_p);
for ($i=0;$i<(int)$_GET['n'];$i++){
$array=array(
date('Y-m-d H:i:s',time()-rand(100,7*24*3600)),
'Auto <150>', 150,'791612345678','n/a','n/a','n/a','n/a','n/a',999, rand(7,999), 'ANSWERED',3,'',uniqid(),'','',''
);
$str=array();
foreach ($array as $v) $str[]="'{$v}'";
$str=implode(', ',$str);
$dbh->query("INSERT INTO cdr VALUES ({$str});");
}
}
/** MakeRequest to asterisk interfacees
* @param $params -- array of req. params
* @return array -- response
*/functionasterisk_req($params,$quick=false){
// lets decide if use AJAM or AMIreturn !defined('AC_PREFIX')?ami_req($params,$quick):ajam_req($params);
}
/**
* Shudown function. Gently close the socket
*/functionasterisk_socket_shutdown(){ami_req(NULL);}
/*** Make request with AMI
* @param $params -- array of req. params
* @param bool $quick -- if we need more than action result
* @return array result of req
*/functionami_req($params,$quick=false){
static $connection;
if ($params===NULLand $connection!==NULL) {
// close connection
fclose($connection);
return;
}
if ($connection===NULL){
$en=$es='';
$connection = fsockopen(AC_HOST, AC_PORT, $en, $es, 3);
// trying to connect. Return an error on failif ($connection) register_shutdown_function('asterisk_socket_shutdown');
else {$connection=NULL; returnarray(0=>array('response'=>'error','message'=>'socket_err:'.$en.'/'.$es));}
}
// building req.
$str=array();
foreach($params as $k=>$v) $str[]="{$k}: {$v}";
$str[]='';
$str=implode("\r\n",$str);
// writing
fwrite($connection,$str."\r\n");
// Setting stream timeout
$seconds=ceil(AC_TIMEOUT);
$ms=round((AC_TIMEOUT-$seconds)*1000000);
stream_set_timeout($connection,$seconds,$ms);
// reading respomse and parsing it
$str= ami_read($connection,$quick);
$r=rawman_parse($str);
//var_dump($r,$str);return $r;
}
/*** Reads data from coinnection
* @param $connection -- active connection
* @param bool $quick -- should we wait for timeout or return an answer after getting command status
* @return string RAW response
*/functionami_read($connection,$quick=false){
$str='';
do {
$line = fgets($connection, 4096);
$str .= $line;
$info = stream_get_meta_data($connection);
if ($quick and $line== "\r\n") break;
}while ($info['timed_out'] == false );
return $str;
}
/*** Echo`s data
* @param $array answer data
* @param bool $no_callback shold we output as JSON or use callback function
*/functionanswer($array,$no_callback=false){
header('Content-type: text/javascript;');
if (!$no_callback) echo"asterisk_cb(".json_encode($array).');';
elseecho json_encode($array);
die();
}
/** Parse RAW response
* @param $lines RAW response
* @return array parsed response
*/functionrawman_parse($lines){
$lines=explode("\n",$lines);
$messages=array();
$message=array();
foreach ($lines as $l){
$l=trim($l);
if (empty($l) and count($message)>0){ $messages[]= $message; $message=array(); continue;}
if (empty($l)) continue;
if (strpos($l,':')===false) continue;
list($k,$v)=explode(':',$l);
$k=strtolower(trim($k));
$v=trim($v);
if (!isset( $message[$k])) $message[$k]=$v;
elseif (!is_array( $message[$k])) $message[$k]=array( $message[$k],$v);
else $message[$k][]=$v;
}
if (count($message)>0) $messages[]= $message;
return $messages;
}
/** Make request via AJAM
* @param $params req. params
* @return array parsed resp.
*/functionajam_req($params){
static $cookie;
// EveryRequest Ajam sends back a cookir, needed for auth handlingif ($cookie===NULL) $cookie='';
// make req. and store cookielist($body,$cookie)= rq(AC_PREFIX.'rawman?'.http_build_query($params),$cookie);
// parse an answerreturn rawman_parse($body);
}
/** make http req. to uri with cookie, parse resp and fetch a new cookie
* @param $url
* @param string $cookie
* @return array ($body,$newcookie)
*/functionrq($url,$cookie=''){
// get RAW data
$r=_rq($url,$cookie);
// divide in 2 partslist($headersRaw,$body)=explode("\r\n\r\n",$r,2);
// parse headers
$headersRaw=explode("\r\n",$headersRaw);
$headers=array();
foreach ($headersRaw as $h){
if (strpos($h,':')===false) continue;
list($hname,$hv)=explode(":",$h,2);
$headers[strtolower(trim($hname))]=trim($hv);
}
// fetch cookieif (!empty($headers['set-cookie'])){
$listcookies=explode(';',$headers['set-cookie']);
foreach ($listcookies as $c){
list($k,$v)=explode('=',trim($c),2);
if ($k=='mansession_id') $cookie=$v;
}
}
returnarray($body,$cookie);
}
/** mare a request to URI and return RAW resp or false on fail
* @param $url
* @param $cookie
* @return bool|string
*/function_rq($url,$cookie){
$errno=$errstr="";
$fp = fsockopen(AC_HOST, AC_PORT, $errno, $errstr, 3);
if (!$fp) returnfalse;
$out = "GET {$url} HTTP/1.1\r\n";
$out .= "Host: ".AC_HOST."\r\n";
if (!empty($cookie)) $out.="Cookie: mansession_id={$cookie}\r\n";
$out .= "Connection: Close\r\n\r\n";
fwrite($fp, $out);
$r='';
while (!feof($fp)) $r.=fgets($fp);
fclose($fp);
return $r;
}
Note!
My explanations for the parameters at the beginning of the script are given directly in the code.
You can check the script operation using the following links (note - I use my login / password and the path to the script, they must be different for you):
_https: //asterisk.vistep.ru/amocrm.php? _Login = amocrm & _secret = JD3clEB8f4-_3ry84gJ & _action = test_cdr
_https: //asterisk.vistep.ru/amocrm.php? _login = amocrm & _secret = JD3clEB8f4-_3ry84gJ & _action = status the
exhaust should be like on
screenshots
test_cdr
status
status
Testing the resulting bunch
Based on the results of the settings, we get the following features:
- call display in amoCRM (if a contact is already there, the name is displayed and you can go to the contact card, if not, then create a new one-click)
- display the name of the contact from amoCRM on the phone with an incoming call
- the ability to make a call from the amoCRM interface in a couple of clicks
- forward the call to the responsible manager by transferring it to a special number
The video format is best for demonstration, so please:
Conclusion
I hope in this article I managed to completely close the issue of integrating amoCRM and Asterisk.
If you have any questions, welcome in the comments.
There is no account on Habré? - My coordinates are a profile, write, I will try to help.
Asterisk is fun!
Good luck to all!