Blog

В одной из заметок, Забираем звонки из Webitel, я описал, как с помощью REST API вы можете забирать информацию о звонках из Webitel. Сегодня мы рассмотрим  на примере Звонки по направлениям, как работать с агрегациями и показателями. Для лучшего понимания примера, настоятельно рекомендую ознакомится с разделом агрегации документации по elasticsearch.

Предположим, что нам нужно рассчитать количество, а так же показатели общей и средней длительности звонков по направлениям за текущие сутки. Тело запроса будет иметь следующий вид:

calls_by_direction.json
{
  "index": "cdr-a",
  "limit": 0,
  "aggs": {
    "direction": {
      "terms": {
        "field": "direction",
        "order": {
          "_count": "desc"
        },
        "size": 5
      },
      "aggs": {
        "talksec_sum": {
          "sum": {
            "field": "talksec"
          }
        },
        "talksec_avg": {
          "avg": {
            "field": "talksec"
          }
        }
      }
    }
  },
  "filter": [
    {
      "bool": {
        "must": [
          {
            "range": {
              "created_time": {
                "gte": "now/d",
                "lte": "now"
              }
            }
          }
        ]
      }
    }
  ]
}

В нашем запросе группировка происходит по полю direction, а среднее и суммарное значение мы вычисляем по полю talksec. Дополнительно мы добавляем фильтр по времени: от текущей даты до начала суток.

Запрос с помощью утилиты cURL будет иметь вид:

curl -s -L -XPOST \
    -H 'Content-Type: application/json' \
    -H 'X-Access-Token: eyJMDU0NjAwMDAwMCwhaW4iLCJ2IjoyfQ.VeWrCqkv_lG1bLVv6tvOFPz2XfhiTpQG8XcFji8gSS4'\
    "https://cloud-eu.webitel.com/engine/api/v2/cdr/text" -d@calls_by_direction.json

В результате мы получим JSON документ с результатом запроса в объекте aggregations, который уже легко можем обработать любимыми инструментами:

ответ
{
  "took": 45,
  "timed_out": false,
  "_shards": {
    "total": 33,
    "successful": 33,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 343,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "direction": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "outbound",
          "doc_count": 209,
          "talksec_sum": {
            "value": 7093
          },
          "talksec_avg": {
            "value": 55.4140625
          }
        },
        {
          "key": "inbound",
          "doc_count": 94,
          "talksec_sum": {
            "value": 2430
          },
          "talksec_avg": {
            "value": 73.63636363636364
          }
        },
        {
          "key": "internal",
          "doc_count": 12,
          "talksec_sum": {
            "value": 172
          },
          "talksec_avg": {
            "value": 24.571428571428573
          }
        }
      ]
    }
  }
}

В данной заметке рассмотрим какие ключевые изменения претерпела Kibana

Права доступа

В версии Webitel 3.11 была добавлена новая администрируемая роль kibana в Access Control List. Обратите внимание на то, что если вы обновили ваш сервер с версии 3.10, тогда вам так же необходимо разрешить нужным группам пользователей доступ в Kibana, иначе ваше пользователи не смогут войти в систему аналитики. Теперь у вас есть возможность выставить права на чтение, создание, изменения и удаление визулазаций либо дашбордов.

Пространства / Spaces

Новый функционал, который позволяет группировать настройки индексов, визуализаций и дашбордов. После обновления все ваши старые графики автоматически конвертируются в новый формат и будут доступны в пространстве Default. Так же, обратите внимание на то, что для обеспечения совместимости с ранними версиями данное пространство не администрируется правами доступа. Если у пользователя есть права на чтение, тогда он всегда будет видеть все визуализации в пространстве Default.

Доступ к пространствам

После создания нового пространства, вы можете указать каким группам пользователей какие права предоставлять к данному Space:

Timelion

Новый тип визуализации Timelion, который позволяет создавать time series графики с использование хорошо документированного синтаксиса:

Canvas

Новый подход к созданию интерактивных экранов

Инструменты разработчика

Для отладки запросов в elasticsearch мы добавили инструмент Dev Tools 

В новой версии Webitel 3.11 мы добавили двухфакторную аутентификацию для пользователя root, что бы обеспечить дополнительную защиту для ваших сервером. Для того, что бы включить данный функционал, вам нужно в файле env/common включить параметр и перезапустить сервис:

application:auth:useTOTP=true

Следующий шаг - перейти в новый пункт меню Security

И активировать защиту:

После чего у вас не экране появится QR код:

Отсканируйте его с помощью приложения Google Authenticator:

Теперь, после ввода пароля для пользователя root, дополнительно нужно будет вводить одноразовый пароль из мобильного приложения:

Еще в 2014 году в Viber добавили поддержку бесплатных исходящих звонков на номера сети iNum. Что такое iNum? iNum (international Number) - международный номер. Данные номера позиционируются как глобальные телефонные номера, не зависящие от географической привязки, текущего местоположения, расстояния и государственных границ. iNum использует телефонный код +883.

Что нужно для подключения номера +883? Самый простой способ - зарегистрироваться на zadarma или youmagic, либо другого SIP провайдера, который предоставляет такие номера. Подключаем номер +883 к Webitel и теперь клиенты могут бесплатно звонить с Viber к нам:

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

Для решения задачи мы воспользуемся приложением CDR, которое позволит нам выполнить запрос в базу elasticsearch и получить среднее время ожидания абонентов в очереди main за последний час. Правильный запрос в elasticsearch будет иметь вот такой вид:

elasticsearch
{
    "size": 0,
    "aggs": {
        "waiting": {
            "avg": {
                "field": "queue.wait_duration"
            }
        }
    },
    "query": {
        "bool": {
            "must": [
                {
                    "match": {
                        "queue.name": "main"
                    }
                },
                {
                    "range": {
                        "created_time": {
                            "gte": "now-1h",
                            "lte": "now"
                        }
                    }
                }
            ]
        }
    }
}

Остается оформить все в нашей схеме ACR: получить целую часть из среднего значения ожидания в очереди, текст озвучим с помощью синтеза речи и т.д.

Привожу простой пример такой схемы:

ACR
[
	{
		"cdr": {
			"exportVar": {
				"avg_wait": "aggregations.waiting.value"
			},
			"elastic": {
				"aggs": {
					"waiting": {
						"avg": {
							"field": "queue.wait_duration"
						}
					}
				},
				"index": "cdr-a*",
				"limit": 0,
				"query": "*",
				"filter": {
					"bool": {
						"must": [
							{
								"match": {
									"queue.name": "main"
								}
							},
							{
								"range": {
									"created_time": {
										"gte": "now-1h",
										"lte": "now"
									}
								}
							}
						]
					}
				}
			}
		}
	},
	{
		"math": {
			"data": "${avg_wait}",
			"setVar": "avg_wait",
			"fn": "ceil"
		}
	},
	{
		"log": "avg_wait: ${avg_wait}"
	},
	{
		"queue": {
			"name": "main",
			"timer": {
				"interval": 1,
				"tries": 1,
				"actions": [
					{
						"ccPosition": {
							"var": "ccPosition"
						}
					},
					{
						"if": {
							"expression": "!${avg_wait} || ${avg_wait} < 60",
							"then": [
								{
									"tts": {
										"text": "Расчетное время ожидания до соединения с оператором меньше одной минуты.",
										"voice": "Maxim"
									}
								}
							]
						}
					},
					{
						"if": {
							"expression": "${avg_wait} > 120",
							"then": [
								{
									"tts": {
										"text": "Расчетное время ожидания до соединения с оператором больше двух минут.",
										"voice": "Maxim"
									}
								}
							],
							"else": [
								{
									"tts": {
										"text": "Расчетное время ожидания до соединения с оператором меньше двух минут.",
										"voice": "Maxim"
									}
								}
							]
						}
					}
				]
			}
		}
	}
]

Довольно часто наши клиенты сталкиваются с необходимостью выгрузить информацию о звонках из раздела Call Detail Record к себе. Как вы уже знаете (даже если взять во внимание предыдущую запись в этом блоге, а именно 📲 Липкость звонка), вся историческая статистика хранится в базе elasticsearch. В документации вы так же могли увидеть пример получения данных с помощью нашего REST API. Но, что если звонков несколько сотен? Или несколько тысяч? Как правильно получить такой объем данных? Сегодня я расскажу как работать с большими объемами данных.

Scroll

Для получения большого количества данных, в elasticsearch предусмотрен функционал scroll, который мы повторили и в нашем REST API. Рассмотрим на примере:

webitel_scroll_request.json
{
    "scroll" : "5m",
    "limit": 1000,
    "sort": {
        "created_time": {
            "order": "desc",
            "unmapped_type": "boolean"
        }
    },
    "index": "cdr-a",
    "query": "*",
    "columns": [
        "created_time",
        "uuid",
        "direction",
        "duration"
    ],
    "filter": [
        {
            "bool": {
                "must": [
                    {
                        "range": {
                            "created_time": {
                                "gte": "now/w",
                                "lte": "now"
                            }
                        }
                    }
                ]
            }
        }
    ]
}

В тело нашего запроса мы добавили 2 новых параметра:

  • scroll - как долго на сервере держать результат запроса
  • limit - какими порциями возвращать результат запроса

Дальше, выполняем первый запрос с указанным телом на REST API, для простоты я использую консольную утилиту cURL:

curl -s -L -XPOST \
    -H 'Content-Type: application/json' \
    -H 'X-Access-Token: ciOiJIUzI1NiJ9.jEyM2UxNThjLWVkNzMtNDAwi'\
    "https://pre.webitel.com/engine/api/v2/cdr/text" -d@webitel_scroll_request.json

Вместе с результатом, который не будет превышать заданного в limit значения, мы получим _scroll_id:

Теперь все последующие запросы мы выполняем уже с scrollId в теле запроса. Пример:

curl -s -L -XPOST \
        -H 'Content-Type: application/json' \
        -H 'X-Access-Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6I9.IjKpitL05OLjUPeUQyd4E'\
        "https://pre.webitel.com/engine/api/v2/cdr/text/scroll" -d '
        {
            "scroll": "5m",
            "scrollId": 'МНОГО_БУКВ_ПОЛУЧЕННОГО_ИД'
        }'

Повторяем запрос, пока не заберем все данные с сервера.

Бонус

В качестве бонуса, подготовил небольшой bash скрипт, который с помощью cURL и jq поможет вам выкачать необходимые данные и сохранить в CSV файл:

#!/bin/bash
#
rm cdr.csv

tmpfile=$(mktemp /tmp/scroll.XXXXXX)
scroll_id=$(curl -s -L -XPOST \
    -H 'Content-Type: application/json' \
    -H 'X-Access-Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEyM2UxNThjLWVkNzMtNDAzOC1hOWExLTA5Y2MxZjk4ZDJmYSIsImV4cCI6MTU0OTQ5MDQwMDAwMCwiZCI6IndlYml0ZWwuZHJydXBpYWguY29tIiwidCI6ImRvbWFpbiIsInYiOjJ9.IjK6q1ra6um1ZJ0_gJImkNcZUpitL05OLjUPeUQyd4E'\
    "https://pre.webitel.com/engine/api/v2/cdr/text" -d@webitel_scroll_request.json | tee >(jq -r '.hits.hits[].fields | [.created_time[], .uuid[], .direction[], .duration[]] | @csv' >>cdr.csv) >(jq '.hits.hits | length' >${tmpfile}) | jq ._scroll_id)
size=$(cat ${tmpfile})
total=$size

while [ $size -ge 1000 ]
do
    size=$(curl -s -L -XPOST \
        -H 'Content-Type: application/json' \
        -H 'X-Access-Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6IjEyM2UxNThjLWVkNzMtNDAzOC1hOWExLTA5Y2MxZjk4ZDJmYSIsImV4cCI6MTU0OTQ5MDQwMDAwMCwiZCI6IndlYml0ZWwuZHJydXBpYWguY29tIiwidCI6ImRvbWFpbiIsInYiOjJ9.IjK6q1ra6um1ZJ0_gJImkNcZUpitL05OLjUPeUQyd4E'\
        "https://pre.webitel.com/engine/api/v2/cdr/text/scroll" -d '
        {
            "scroll": "5m",
            "scrollId": '${scroll_id}'
        }' | tee >(jq -r '.hits.hits[].fields | [.created_time[], .uuid[], .direction[], .duration[]] | @csv' >>cdr.csv) | jq '.hits.hits | length')
    echo $size
    total=$(( $total + $size ))
done

echo "$total - done"

rm $tmpfile
exit 0

Тело запроса должно находится в файле webitel_scroll_request.json в возле данного скрипта.

Удачи с запросами!

Использование IVR меню, в большинстве случаев, позволяет эффективно распределять входящие звонки и снижать нагрузку с секретаря. Клиент звонит, выбирает нужный отдел либо донабирает внутренний номер сотрудника и достигает цели. Но, как быть в ситуации, когда клиент не знает кому он звонит? Что если это наш менеджер не смог дозвонится клиенту, который перезвонил и попал на IVR? В данном случае будет полезным функционал "Липкости звонка". Мы уже рассматривали реализацию: Входящий звонок с маршрутизацией на ответственного в bpm'online. Сегодня мы рассмотрим пример маршрутизации в первую очередь на того, кто сегодня уже общался с данным номером либо звонил ему. Приступим!

Kibana нам в помощь

Для решения поставленной задачи нам понадобится приложение cdr, которое предназначено для поиска по журналу звонков в elasticsearch. В начале, нам нужно написать правильный запрос. Делаем тестовый звонок и открываем интерфейс Kibana, раздел Discover. Выбираем для отображения колонки extension (номер сотрудника), caller_id_number (номер абонента), destination (номер назначения).

Теперь сделаем запрос с фильтрацией по номеру абонента, он может быть либо в номере назначения (для исходящих) либо в номере абонента (для входящих):

destination_number:/0969716158/ OR caller_id_number:/0969716158/

Как результат, мы получаем номер сотрудника, extension, который общался либо звонил абоненту. И обязательно открываем Response (ответ со стороны elasticsearch), он нам пригодится ниже:

И так, как искать мы уже поняли, а теперь, добавим это все в маршрутизацию на стороне Webitel.

Запрос в CDR

Для работы с приложением cdr, нам нужно написать правильно запрос в elasticsearch

{
    "limit": 1,
    "sort": {
        "created_time": {
            "order": "desc",
            "unmapped_type": "boolean"
        }
    },
    "index": "cdr-a",
    "query": "*",
    "columns": [
        "extension"
    ],
    "filter": [
        {
            "bool": {
                "must": [
                    {
                        "range": {
                            "created_time": {
                                "gte": "now/d",
                                "lte": "now"
                            }
                        }
                    },
                    {
                        "query_string": {
                            "query": "destination_number:/.*${caller_id_number}/ OR caller_id_number:/.*${caller_id_number}/",
                            "analyze_wildcard": true,
                            "default_field": "*"
                        }
                    }
                ]
            }
        }
    ]
}

Разберем запрос:

ПараметрОписание
limit
Количество записей. 1 - так как нам нужно только последнего звонившего.
sort
Сортировку делаем от новых к старым, опять же - только последний.
index
В каком индексе осуществлять поиск: cdr-a
columns
Какие поля возвращать - нам нужен номер сотрудника: extension
query_string
Строка запроса в которой номер телефона заменен на канальную переменную caller_id_number
range
Временной интервал: начиная с 00:00 текущего дня до теперь.

А теперь посмотрим на ответ со стороны elasticsearh:

{
    "took": 4,
    "timed_out": false,
    "_shards": {
        "total": 10,
        "successful": 10,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": 5,
        "max_score": null,
        "hits": [
            {
                "_index": "cdr-a-2019-.bpmonline.com",
                "_type": "cdr",
                "_id": "bb643481-310a-45cb-8c4e-95244c8e02b8",
                "_score": null,
                "fields": {
                    "extension": [
                        "115"
                    ]
                },
                "sort": [
                    1547032525216
                ]
            }
        ]
    }
}

Что бы добраться к нужному полю extension, мы должны пройти путь:  hits => hits => fields => extension

Теперь давайте обновим нашу маршрутизацию в Public

Маршрутизация входящего звонка

У нас уже есть готовое IVR меню, так что мы добавим вначале проверку, и если результат не пустой, попытаемся соединить с нужным сотрудников (а с помощью userData добавим еще условие, что бы он был в статусе onhook - Готов). Если сотрудник не ответит в течение 15 секунд, тогда уже продолжим выполнять нашу типовую IVR схему:

["начало схемы"],

    {
        "cdr": {
            "exportVar": {
                "last_cid": "hits.hits.0.fields.extension.0"
            },
            "elastic": {
                "limit": 1,
                "sort": {
                    "created_time": {
                        "order": "desc",
                        "unmapped_type": "boolean"
                    }
                },
                "index": "cdr-a",
                "query": "*",
                "columns": [
                    "extension"
                ],
                "filter": [
                    {
                        "bool": {
                            "must": [
                                {
                                    "range": {
                                        "created_time": {
                                            "gte": "now/d",
                                            "lte": "now"
                                        }
                                    }
                                },
                                {
                                    "query_string": {
                                        "query": "caller_id_number:/.*${caller_id_number}/ AND destination_number:${destination_number}",
                                        "analyze_wildcard": true,
                                        "default_field": "*"
                                    }
                                }
                            ]
                        }
                    }
                ]
            }
        }
    },
    {
        "if": {
            "expression": "${last_cid}",
            "then": [
                {
                    "userData": {
                        "name": "${last_cid}",
                        "var": "account_state",
                        "setVar": "acc_state"
                    }
                },
                {
                    "log": "CID: ${caller_id_number}, Ext: ${last_cid} (${acc_state})"
                },
                {
                    "if": {
                        "expression": "${last_cid} && ${acc_state} == 'onhook'",
                        "then": [
                            {
                               "bridge": {
                                   "endpoints": [
                                         {
                                           "name": "${last_cid}",
                                           "type": "user",
                                           "parameters": [
                                              "leg_timeout=15"
                                           ]
                                        }
                                     ]
                                 }
                            }
                        ]
                    }
                }
            ]
        }
    },

["продолжение схемы"]

Все прекрасно знают, что с помощью Календаря мы можем настроить рабочий график офиса и добавить проверку в маршрутизации на рабочий\не рабочий день. Но, бывают ситуации, когда этого не достаточно. Мы хотим настроить разделение на рабочие, не рабочие и праздничные дни. Поскольку календарь умеет возвращать только истина либо ложь в отношение рабочего графика, то раньше для решения данной задачи приходилось создавать 2 календаря - в одном проверяем на праздники, а во втором - на рабочие. С релизом 3.10 нам достаточно 1 календарь :allthethings:

Как это работает? Появился новый параметр extended, который отключен, по умолчанию, для сохранения совместимости со старыми схемами. Проверяем календарь включенным параметром:

{
    "calendar": {
        "name": "my Business Calendar",
        "extended": true,
        "setVar": "isWorkDay"
    }
}

Теперь переменная isWorkDay может принимать следующие значения:

  • true - сейчас рабочий день
  • false - не рабочий день
  • holiday - праздничный день
  • ahead - календарь еще не стартовал
  • expire - календарь уже завершился

Надеюсь, что такое изменение поможет эффективней создавать ваши схемы маршрутизации.

Сегодня мы поговорим о SIP телефонах. А именно, об опыте использования SIP телефонов в локальной сети офиса, которые подключаются к SIP серверу через публичную сеть Интернет. Если вы используете SIP-телефоны вместе с нашим облачным сервисом, то данная заметка будет полезна и поможет избежать основных проблем при работе IP телефонии за NAT.

Что такое NAT?

Начнем с того, а что же такое этот NAT?

Не буду копировать из wiki умные вещи, попробую объяснить проще - NAT (Network Address Translation) — это механизм, который позволяет маршрутизатору (наш сервер, роутер, модем – все, что используем для выхода в Интернет) определять какие сервисы находятся за роутером и должны быть доступны из интернета, чтобы пользователи оттуда могли этими сервисами пользоваться. Так как, в большинстве случаев, у нас всего 1 внешний (белый, публичный – как кому больше нравится) IP адрес, а устройств в сети много, то мы используем локальные (серые) IP адреса. Они не доступны из Интернета, а NAT помогает нам опубликовать в мир какой-то порт из локальной сети.

Надеюсь, что здесь пока все понятно…

Проблема телефонии за NAT

Начнем с голоса. Вряд ли стоит объяснять, почему NAT является проблемой для голосового трафика. Если SIP протокол использует 1 статический порт на аппарате, то для голоса такой порт назначается динамически при каждом новом звонке. Как результат, каждый из нас знаком с ситуацией в IP-телефонией, когда голос ходит только в одном направление или вовсе отсутствует. Сразу следует заметить, что универсального «лекарства» здесь нет: все решения этой проблемы в большей или меньшей степени частные. Впрочем, проблема обхода NAT голосовым потоком – это лишь одна сторона медали. Вначале мы рассмотрим, какие препятствия создаёт NAT для сигнальных сообщений (в таких случаях звонок вообще не попадает на телефон за NAT) и какие расширения SIP были разработаны для их преодоления.

Обход NAT SIP-сигнализацией

При использовании UDP протокола, отправка ответа на SIP-запрос осуществляется на тот IP-адрес, с которого запрос был получен. Номер же порта для отправки извлекается из заголовка Via в SIP пакете. В случае использования NAT – это порт, на котором ожидает ответа наш IP телефон, но точно не тот порт, через который происходит NAT-трансляция и на котором NAT ожидает поступления ответа, чтобы его дальше направить уже на телефон. Мы получаем ситуацию, в которой звонок не может достичь SIP телефона:

Для решения этой проблемы SIP умеет отправлять ответ на порт, с которого запрос был получен, вместо порта, взятого из заголовка Via. При этом сам порт заносится в специальный параметр rport-заголовка Via. Это позволяет ответу найти соответствие в таблице NAT и достичь целевого узла. Этот метод называется симметричной маршрутизацией ответов.

Другая проблема заключается в том, что каждая запись в таблице трансляции NAT автоматически удаляется после определенного промежутка времени. Это актуально не только для установления нового соединения, но и во время SIP-диалогов новые сообщения также могут не поступать в течение длительного промежутка времени, чего достаточно для того, чтобы запись из таблицы была удалена. В таких случаях мы можем наблюдать ситуацию, что звонок разрывается точно на 30-40 секунде.

Для решения данной проблемы, SIP умеет периодически отправлять запросы re-INVITE, OPTIONS, INFO, NOTIFY либо другие. В итоге мы получаем вот такую картинку:

Обход NAT медиатрафиком

Решение проблемы обхода NAT медиатрафиком требует более сложных изменений, поскольку необходимо заменить IP-адрес и номер порта, анонсированные в SDP-сообщении, таковыми, что обеспечат доставку потоков нужному адресату за NAT. Есть несколько способов решения данной проблемы, но, так как данная заметка уже получается довольно большой, я остановлюсь только на прохождении SIP, так как именно с этим чаще всего сталкиваются наши клиенты.

Рекомендации по настройке SIP телефонов

Несколько основных рекомендаций по настройке SIP телефонов в сети (на примере аппарата Yealink).

RPort

Включить параметр rport:

 

Keep Alive

Включить и отправлять каждые 30 секунд Keep Alive. У других производителей телефонов может называтся: re-INVITE, OPTIONS, INFO, NOTIFY

Разные локальный SIP

В некоторых случаях помогает, если каждому аппарату задать свой уникальный локальный SIP порт:

TCP/TLS

Также можно использовать TCP либо TLS вместо UDP. В некоторых случаях это более надежное решение для обхода NAT. При использовании TLS, следует обратить внимание, что порт подключения к webitel нужно указать 5071, вместо стандартного для UDP/TCP 5070

Отдельная сеть

Хорошей практикой является выделение для SIP телефонов отельной локальной сети с QoS приоритезацией голосового трафика. Еще лучше – настроить отдельный VLAN под IP телефонию.

Поскольку все чаще встречаюсь с непониманием того, что творится у нас в настройках маршрутизации, решил сегодня написать несколько слов о регулярных выражениях (smile)

Кода Вы открываете настройку исходящей маршрутизации, то можете увидеть вот такой кошмар:

Давайте на данном примере попытаемся понять, что и как работает.

^\+?38?(0[679]3\d{7})$

Данной регулярное выражение описывает коды украинского мобильного оператора lifecell. У данного оператора есть 3 кода: 63, 73 и 93. Номера телефонов пользователи могут набрать как в международном формате +38063ххххххх, в национальном формате 073ххххххх, так и вообще в устаревшем формате: 8093ххххххх. Вот таким выражением мы закрываем все варианты набора номера.

Рассмотрим более детальней:

  • ^ - начало регулярного выражения. Если не будет этого символа, тогда у нас получиться вхождение, а нам нужно нужно проверять с самого начала набранного номера. Значит для нас это обязательный символ.
  • \+? - дальше проверка на наличие +, поскольку это служебный символ, то мы его экранируем с помощью \. А вот наличие знака вопроса, ?, означает не обязательность +. Он может быть, а может и не быть - как-то так...
  • 3? - а это просто 3, которой вполне может и не быть (помните про наличие знака вопроса после цифры ?)
  • 8? - здесь то все понятно?
  • ( - начало блока совпадения. Все, что будет в круглый скобках, потом попадает в служебные переменные и может использоваться в маршрутизации звонков.
  • 0 - обязательно должен быть 0. Посмотрите на примеры выше - 0 всегда присутствует.
  • [679] - дальше должна идти одна из 3-х цифр: или 6, или 7 или же 9. Одна! Не три, а одна из 3 - просто уточняю (wink)
  • 3 - а здесь обязательно должна быть тройка.
  • \d - этот незамысловатый знак говорит нам о наличие любой цифры (то же самое, если бы я написал вот так: [0-9]).
  • {7} - а теперь мы говорим о количестве повторений предыдущего выражения. Это означает, что любых цифр всего должно быть 7.
  • ) - закрываем блок совпадения.
  • $ - завершение регулярного выражения. Опять же, без него получается вхождение, поэтому для нас данный символ будет обязательным.


Для закрепления, рассмотрим еще один пример:

^\+?(7|8)(\d{10,12})$

Это регулярное выражение описывает все телефонные коды РФ (если честно, то и Казахстана, но, сейчас не об этом). Посмотрим только отличие от предыдущего

  • 7|8 - вертикальная черточка говорит об ИЛИ. Может быть 8 либо 7 - одна из двух 
  • {10,12} - опять количество повторений предыдущего выражения. Но, здесь у нас диапазон - не меньше 10 и не больше 12.

Так же, в отличие от предыдущего выражения, здесь у нас дважды встречаются круглые скобки, а это означает, что мы отдельно можем работать с первым и вторым совпадением. Что это означает? Давайте посмотрим на очень полезную функцию, которая должна появиться в следующем релизе - тест регулярного выражения:

  • &reg0.$0 - если нам нужен номер целиком
  • &reg0.$1 - если только совпадение в первых круглых скобках
  • &reg0.$2 - если совпадение во вторых круглых скобках

Провайдер требует от нас всегда присылать номера в национальном формате через 8. Для того, что бы набранный мною номер +74997045627 уходил к провайдеру в формате 84997045627, наш bridge должен быть вот таким:

{
	"bridge": {
		"endpoints": [
			{
				"dialString": "8&reg0.$2",
				"name": "myMskGw",
				"type": "sipGateway"
			}
		]
	}
}

Надеюсь, что теперь стало немного понятьней, что такое регулярное выражение и как его правильно прочитать.

Приветствую!

Как известно в текущей версии дайлера webitel есть возможность загрузить абонентов из CSV файла:

Вы можете настроить сопоставление колонок в файле и выполнить импорт. Но, есть одно неудобство, если вам нужно каждый день выполнять такую загрузку, то каждый день вы сталкиваетесь с настройкой этих колонок. Уже довольно давно наши клиенты спрашивали, как это можно "запомнить", и мы решили в следующем обновление пересмотреть данный механизм. И так, у нас появляется новая вкладка Integrations, которая позволяет предварительно настроить нужные нам шаблоны для импорта абонентов либо выгрузки результатов работы дайлера:

Уже сейчас есть возможность работать с CSV файлами и с таблицами MSSQL (через внешнюю утилиту). Более того, для SQL предусмотрена настройка планировщика, который будет автоматически по расписанию забирать данные либо выгружать статистику.

Каких еще провайдеров были бы интересно увидеть в этом меню?

Пишите в комментариях :awthanks: