Asterisk: отслеживаем события AMI и отправляем POST запрос в CRM (Python)


Поступила задача связать Asterisk c неизвестной CRM системой. Среди прочего требовалось отправлять POST запрос в формате JSON в момент ответа оператором на входящий звонок. В момент, когда оператор поднимает трубку - необходимо в CRM открывать для него карточку клиента/заказа.

Итак, можно было бы отправлять запрос напрямую из диалплана. Нам этот вариант не подойдет, но возможность эту мы рассмотрим. В случае с Dial, а параметрах указываем ссылку на контекст, в который необходимо перейти при ответе и передаем туда необходимые переменные. Как-то так

same => n,Dial(SIP/100,60,iU(answer-script,${CALLERID(num)}))
и далее в контексте делаем, что нам нужно:
[answer-script]
exten => s,1,NoOp(Выполняем действия с входящим номером ${ARG1})
same => n,Return
В случае с очередью можно было бы поступить как-то так:
;идем в очередь на 60 секунд и при ответе перемещаемся в answer-script
same => n,Queue(queue,tT,,,60,,,answer-script^${CALLERID(num)})

Но в моем случае данные данные способы не подходят, так как не получается выловить номер конкретного ответившего оператора. К тому-же это решение не совсем удобное при большом количестве входящих звонков. Нам нужен отдельный сервис, который будет слушать события Asterisk и при нахождении нужных нам выполнять какие-то действия.

События слушать будем через Asterisk Managment Interface (AMI) и при нахождении необходимого, отправляем данные POST запросом. Для этого напишем скрипт на Python и сделаем из него сервис.

Не забудьте сначала создать пользователя для работы с AMI, например таким образом (у меня он с полными правами, вы может ограничить только чтением событий):

nano /etc/asterisk/manager.conf
[general]
enabled = yes
port = 5038
bindaddr = 0.0.0.0
allowmultiplelogin = yes ;несколько параллельных подключений с одним именем

[crm]
secret = pass
deny = 0.0.0.0/0.0.0.0
permit = 127.0.0.1/255.255.255.0
read=system,call,log,verbose,command,agent,user,config,command,dtmf,reporting,cdr,dialplan,originate
write=system,call,log,verbose,command,agent,user,config,command,dtmf,reporting,cdr,dialplan,originate
Устанавливаем зависимости:

curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python3 get-pip.py
pip3 install panoramisk

или в моем случае с Ubuntu

apt-get install python3-panoramisk
apt install python3-requests

Открываем доступ до AMI

nano /etc/asterisk/manager.conf
Путем просмотра событий в момент ответа на звонок выяснилось, что нам необходим Event: BridgeEnter. В нем есть вся необходимая информация: на какой транк пришел вызов, какой оператор ответил, какой был номер клиента. В событии нам необходимо проверять следующие значения:

ChannelStateDesc: Up - оператор поднял трубку
ConnectedLineNum: 100 - внутренний номер ответившего оператора
Exten: 7XXXXXXXXXX - наш внешний номер (имя транка)
CallerIDNum: 7XXXXXXXXXX - номер звонящего клиента
Context: incoming-queue - контекст, куда пришел вызов (это необходимо учитывать, чтобы не было лишних срабатываний, к примеру при локальных звoнкаx)

Мы возьмем библиотеку Panoramisk для доступа в AMI. Скрипт получается вот таким:

nano /etc/asterisk/scripts/crm_callback.py
#!/usr/bin/python3
# -*- coding: utf-8 -*-

import asyncio
from panoramisk import Manager
import threading, queue
import requests, json
import pickle

# API метод CRM
url = 'https://crm-name.ru/call'

# данные для доступа к AMI
ip = '127.0.0.1'
username =  'crm'
secret = 'pass'

exiting = False

manager = Manager(loop=asyncio.get_event_loop(),
                  host=ip,
                  username=username,
                  secret=secret)

# Смотрим событие BridgeEnter c необходимыми полями
@manager.register_event('BridgeEnter')
def BridgeEnter(manager, message):
    global q
# указываем контексты для звонка
    if (message.Context not in ('incoming-queue', 'incoming')):
        return
# нам нужно, чтобы значение ChannelStateDesc было Up
    if (message.ChannelStateDesc != 'Up'):
        return

# выгребаем необходимые данные
    data = {
      'our_external_trunk' : message.Exten,
      'our_internal_number' : message.ConnectedLineNum,
      'client_phone_number' : message.CallerIDNum
    }
    q.put(data)

# отправка POST запроса в CRM
def crm_post(data):    headers = {'Content-type': 'application/json',  # Определение типа данных
           'Accept': 'text/plain',
           'Content-Encoding': 'utf-8'}
    d1 = json.dumps(data)

# выводим получившийся запрос

    print(d1)

# отправляем

    answer = requests.post(url, data=d1, headers=headers)

# тред и очередь сделаны для отправки POST запросов,
# чтобы от Asterisk данные принимать без задержек
def post_thread():
    global q, exiting
    while not exiting:
        data = q.get()
        if (data == 'exit'): break
        try:
            crm_post(data)
        except:
            print('error post')

q = queue.Queue()
threading.Thread(target=post_thread).start()

def main():
    manager.connect()
    try:
        manager.loop.run_forever()
    except KeyboardInterrupt:
        exiting = True
        q.put('exit')
        manager.loop.close()

if __name__ == '__main__':
    main()

Делаем исполняемым
chmod +x /etc/asterisk/scripts/crm_callback.py
Можете проверить работу скрипта запустив его. Если все работает, создадим сервис из него с помощью systemd
nano /lib/systemd/system/crm-asterisk-callback.service
[Unit]
Description=crm calls post service
Wants=network-online.target
After=network.target network-online.target

[Service]
WorkingDirectory=/etc/asterisk/scripts/
Type=idle
ExecStart=/etc/asterisk/scripts/crm_callback.py
KillMode=control-group
Restart=always
RestartSec=10
StandardOutput=null

[Install]
WantedBy=multi-user.target
Далее добавляем в автозагрузку и запускаем сервис
sudo systemctl enable crm-asterisk-callback
sudo systemctl start crm-asterisk-callback

Комментариев нет:

Отправить комментарий