Asterisk: Общие сведения о настройке интеграции

Введение

Asterisk очень доступный инструмент для создания компьютерной телефонии внутри компании. Вместе с этим у Asterisk'a нет одного способа поставки и способа использования, поэтому настройка интеграции конкретной установки Asterisk должна производиться ответственным сотрудником компании.

Порядок действий при подключении

  1. Получить от Qolio доступ к аккаунту админа в вашем кабинете.

  2. Для начала со стороны телефонии нужно определить следующие вещи:

    • где будут храниться записи звонков и как к этим записям можно получить доступ по http(s). В системе есть возможность настроить доступ через S3 совместимое облачное хранилище и настроить доступ к хранилищу с basic http авторизацией.

    • по какому атрибуту будет привязываться звонок к оператору внутри Qolio. Для этой привязки используется поле operator_id в API запросе https://wiki.qolio.io/qolio-or-baza-znanii/integracii/podklyuchenie-po-api/sozdanie-i-upravlenie-polzovatelskimi-polyami-pri-integracii-po-api

  3. Добавить пользователей, по которым будут приходить звонки в Qolio (если этого не сделать, звонки, переданные по API сохраняться не будут). Пользователь добавляет админ телефонии либо другой сотрудник компании.

    Для импорта звонков пользователи должны быть загружены в систему DealApp, а так же быть активными и назначенными на отдел.

  4. Далее следует настроить структуру компании и распределить пользователей по отделам. В дальнейшем звонки будут добавляться к тем отделам, в которых состоят пользователи.

  5. Разработать расширение для телефонии, которая будет передавать через http запросы на DealApp API информацию о совершенных звонках на телефонии. Информацию про API запросы, которые нужно делать для импорта звонков телефонии. Так же можно попробовать делать запросы со страницы настройки интеграции. Далее в этой статье описан пример создания скрипта для синхронизации данных.

Для последующей эксплуатации необходимо продумать, как будут обновляться пользователи, какие роли будут в системе, как они будут взаимодействовать и так далее. На этом моменте технические задачи заканчиваются.

Пример создания скрипта для синхронизации данных Asterisk по API

Для успешной работы Qolio должен принимать данные о телефонных звонках через API по http(s). Для упрощения работы используется "Custom HTTP интеграция", подробности о которой можно почитать тут: https://wiki.qolio.io/qolio-or-baza-znanii/integracii/podklyuchenie-po-api/integraciya-po-api-dlya-ip-telefonii-i-chatov

Qolio необходимо принимать следующие данные о звонках:

  • ID оператора в системе (operator_id) - это идентификатор оператора в системе, с которым связан звонок.

  • Длительность (duration) и направление звонка (direction)

  • Номер звонящего клиента (client_phone_number)

  • URL Ссылку на запись разговора (media_url), по которой можно получить доступ к записи разговора. Эта ссылка будет передаваться в плеер сервиса Qolio.

Также есть возможность расширить набор этих данных через поле metacontent (custom_fields). Подробности можете прочитать в статье про Custom HTTP интеграцию.

В данной статье приводится пример работы, которую можно выполнить, чтобы подключить Asterisk к DealApp

Порядок работы

Самым популярным способом интеграции Asterisk с Qolio является следующий сценарий:

  • создается systemd сервис, который запускается периодически каждые пол часа как cron job

  • этот сервис запускает скрипт, который подключается к базе данных и вытаскивает данные о звонках за последнее время

  • далее скрипт преобразовывает данные из базы данных в необходимый JSON формат и отправляет их в Qolio

Далее будет приведен примеры сервисов, которые были созданы на основе Red Hat Linux (Linux ats 3.10.0) с Asterisk 13.32.0, Python 2.7.5 и Mysql 5.5 (Ver 15.1 Distrib 5.5.65-MariaDB). Python был выбран как скриптовой язык из-за того, что он с большой вероятностью будет находиться в дистрибутиве с Asterisk.

Чаще всего записи телефонных звонков доступны через сетевое хранилище и телефонный разговор в нем можно найти по названию файла, которое храниться в базе CDR.

Скрипт для загрузки телефонных звонков в DealApp

Первым делом подготовим папку и необходимые пакеты для работы скрипта:

// sudo pip install mysql-connector-python
sudo pip install requests

sudo mkdir -p /etc/dealapp-sync/bin

sudo touch /etc/dealapp-sync/bin/dealapp-sync
sudo chmod +x /etc/dealapp-sync/bin/dealapp-sync

В папке /etc/dealapp-sync будет храниться все данные о синхронизации, в том числе и файлик со временем последней синхронизации.

Нам необходимо так же установить pip пакет mysql-connector-python для работы с mysql базы данных и requests для того, чтобы делать http запросы в DealApp сервис.

Далее рассмотрим сам скрипт для синхронизации. Данный скрипт является примером и в конкретном случае его скорее всего придется изменять.

Содержание скрипта /etc/dealapp-sync/bin/dealapp-sync

// #!/usr/bin/env python

import argparse, sys, time, re, logging, datetime, mysql.connector, json, os.path, glob, requests

from logging import critical, error, info, warning, debug
from datetime import datetime, timedelta
from os import path

INTEGRATION_URL="https://api.prod1.dealapp.io/api/v1/integrations/8a48a9d6-1930-47ca-a331-e60e1945d249/phone_calls"
AUTHORIZATION_TOKEN="253aac176651c106c7fcf57acf324559b8e5aa0a27ba1a2db88d325256a3df15fbff1eb353a036faf065d167a7c237d9eacfe217212306f554d541e8321acde1"
RECORDS_STORAGE_BASE_URL="https://ats-srv.local"
DEFAULT_CONFIG_PATH="/etc/dealapp-sync/last_export_timestamp"
INTERNAL_NUMBER_MATCHER=re.compile('^\d{3}$')
CDR_DATABASE_NAME="asteriskcdrdb"


class PhoneCall:
  def __init__(self, clid, calldate, src, dst, billsec, recordingfile):
    self.clid = clid
    self.calldate = calldate
    self.src = src
    self.dst = dst
    self.billsec = billsec
    self.recordingfile = recordingfile

  def is_valid(self):
    return (bool(INTERNAL_NUMBER_MATCHER.match(self.dst)) or bool(INTERNAL_NUMBER_MATCHER.match(self.src))) and self.billsec > 0

  def is_incoming(self):
    return bool(INTERNAL_NUMBER_MATCHER.match(self.dst))

  def direction(self):
    return "incoming" if self.is_incoming() else "outcoming"

  def operator_id(self):
    return self.dst if self.is_incoming() else self.src

  def client_phone_number(self):
    return self.src if self.is_incoming() else self.dst

  def duration(self):
    return float(self.billsec)

  def started_at(self):
    return self.calldate.isoformat()

  def media_url(self):
    return "{}/{}/{}/{}/{}".format(RECORDS_STORAGE_BASE_URL, self.calldate.year, self.calldate.strftime("%m"), self.calldate.strftime("%d"), self.recordingfile)


def get_last_export_timestamp():
  try:
    with open(DEFAULT_CONFIG_PATH, 'r') as config_file:
      return datetime.strptime(config_file.read(), "%Y-%m-%d %H:%M")
  except:
    print "Can not read config file with synchronization timestamp, exporting phone calls created 0.5h ago"
    return datetime.now() - timedelta(hours = 26)


def save_last_export_timestamp(syncronization_start_time):
  try:
    with open(DEFAULT_CONFIG_PATH, 'w') as config_file:
      config_file.write(syncronization_start_time.strftime("%Y-%m-%d %H:%M"))
  except:
    print "Can not save last export time into {}".format(DEFAULT_CONFIG_PATH)


def read_phone_calls_from_database(fetch_from_time):
  cnx = mysql.connector.connect(database=CDR_DATABASE_NAME)
  cursor = cnx.cursor()
  query = ("SELECT clid, calldate, src, dst, billsec, recordingfile FROM cdr "
           "WHERE calldate > %s "
           "AND billsec > 0 "
           "AND COALESCE (recordingfile, '') <> ''")
  cursor.execute(query, (fetch_from_time,))
  phone_calls = []
  for (clid, calldate, src, dst, billsec, recordingfile) in cursor:
    phone_calls.append(PhoneCall(clid, calldate, src, dst, billsec, recordingfile))
  cursor.close()
  cnx.close()
  return phone_calls


def send_phone_calls_to_dealapp(phone_calls):
  phone_calls_are_sent_successfully = True
  headers = {'Content-Type': 'application/json', 'Authorization': AUTHORIZATION_TOKEN}

  for phone_call in phone_calls:
    if phone_call.is_valid():
      serialized_phone_call = {
        "operator_id":         phone_call.operator_id(),
        "direction":           phone_call.direction(),
        "duration":            phone_call.duration(),
        "started_at":          phone_call.started_at(),
        "client_phone_number": phone_call.client_phone_number(),
        "media_url":           phone_call.media_url()
      }
      try:
        result = requests.post(url=INTEGRATION_URL, data=json.dumps(serialized_phone_call), headers=headers)
        if(result.status_code != 200):
          print("Phone call with clid {} can not be sent to DealApp".format(phone_call.clid))
          print(result.text, "Reason")
      except:
        phone_calls_are_sent_successfully = False
        print("Can't connect to DealApp service")

  return phone_calls_are_sent_successfully


def main():
  fetch_from_time = get_last_export_timestamp()
  last_export_time = datetime.now()
  phone_calls = read_phone_calls_from_database(fetch_from_time)
  successeed = send_phone_calls_to_dealapp(phone_calls)
  if(successeed):
    save_last_export_timestamp(last_export_time)


if __name__ == '__main__':
    main()

В этом скрипте есть следующие константы:

Константы

НазваниеОписание

INTEGRATION_URL

Значение AUTHORIZATION_TOKEN берется из модального диалога интеграции (Токен интеграции)

AUTHORIZATION_TOKEN

Значение INTEGRATION_URL берется из модального диалога интеграции (URL интегрируемого приложения)

RECORDS_STORAGE_BASE_URL

Адрес сетевого хранилища записей. В этом виде может выступать адрес S3 bucket или ссылка nginx сервер, который раздает звонки

DEFAULT_CONFIG_PATH

Адрес на файл со временем последней синхронизации

INTERNAL_NUMBER_MATCHER

С помощью этого регулярного выражения проверяется являться данный телефонный разговор внутренним или нет

CDR_DATABASE_NAME

Название базы данных CDR, которое необходимо для подключения к этой базе. В общем случае это значение можно найти в файле /etc/asterisk/cdr_adaptive_odbc.conf

PhoneCall класс используется для того, чтобы принимать данные из базы данных и сериализовать их в значения, которые ожидает получить Qolio.

Название методаОписание

is_valid

Позволяет определить стоит ли экспортировать этот телефонный звонок (например, если звонок не состоялся или состоялся между внутренними номерами)

is_incoming

Определяет, является ли телефонный звонок входящим

direction

Возвращает значения "incoming" или "outcoming" в зависимости от направления разговора

duration

Длительность разговора в секундах. Обратите внимание, что здесь мы берем значение из поля billsec в базе данных, так как оно отображает реальную длительность разговора между оператором и клиентом без времени ожидания.

operator_id

Идентификатор оператора в системе. В нашем случае это значение его внутреннего номера, это значение должно быть настроено в системе DealApp.

client_phone_number

Телефонный номер клиента, с которым происходил разговор

started_at

Время разговора в ISO8601 формате

media_url

Ссылка на запись разговора, которая будет использоваться в плеере внутри DealApp. В нашем случае ссылка формируется из адреса хранилища данных, данных о дате звонка и названия записи

Описаний функций скрипта

NameОписание

get_last_export_timestamp

Получает время последней синхронизации с DealApp. Далее это время используется, чтобы доставать информацию о звонках из базы CDR

save_last_export_timestamp

Записывает время последней успешной синхронизации с DealApp в файл

read_phone_calls_from_database

Делает запрос в базу данных на получение записей после определенного времени и оборачивает результат в объекты типа PhoneCall

send_phone_calls_to_dealapp

Принимает список телефонных звонков, преобразует их в JSON и отправляет на сервис DealApp

Создание systemd сервиса для синхронизации

Сервис для вызова скрипта будет выглядеть следующим образом:

// # /etc/systemd/system/dealapp-sync.service

[Unit]
Description=Export Phone Calls from Asterisk CDR to DealApp
Wants=shopify-recent.timer

[Service]
WorkingDirectory=/etc/dealapp-sync
ExecStart=/etc/dealapp-sync/bin/dealapp-sync
User=root
Group=root

[Install]
WantedBy=multi-user.target

Сервис для периодического вызова скрипта для синхронизации будет выглядеть так:

// #/etc/systemd/system/dealapp-sync.timer

[Unit]
Description=Run export of Phone Calls from Asterisk CDR to DealApp every 30 minutes
Requires=dealapp-sync.service

[Timer]
Unit=dealapp-sync.service
OnUnitInactiveSec=30m

[Install]
WantedBy=timers.target

Затем создадим скрипты для systemd сервиса и таймера

// sudo systemctl daemon-reload
sudo systemctl enable dealapp-sync.timer
sudo systemctl start dealapp-sync.timer

Last updated