Слушатель AMI с возможностью фильтрования ивентов и отправкой уведомлений о звонке в телеграм
Статья описывает разработку слушателя AMI (Asterisk Manager Interface), который позволяет фильтровать события и отправлять уведомления о звонках в Telegram.
В статье рассматривается процесс подключения слушателя AMI к Asterisk, настройка фильтров для отслеживания нужных событий и отправки уведомлений в Telegram.
Такой слушатель AMI может быть полезен, например, для мониторинга работы call-центра или других телефонных систем, где необходимо отслеживать определенные события и быстро реагировать на них. Отправка уведомлений в Telegram позволяет получать информацию о звонках и других событиях в режиме реального времени, даже если вы не находитесь за компьютером или не имеете доступа к почте.
В статье используется:
golang: go1.18.4
asterisk: 16
FreePBX 14.0.16.9
библиотека: github.com/Syfaro/telegram-bot-api
Подготовка
- Переходим в файл
/etc/asterisk/manager_custom.conf
и создаем там нового пользователя для ami
- После чего переходим в asterisk -rvvv и выполняем команду manager reload
- Далее идем в telegram @BotFather и создаем там бота
- Пишем команду
/newbot
- Придумываем имя для нашего бота. Далее пишем имя бота, в котором должно быть обязательно слово bot
- После чего получаем наш токен, который понадобится для отправки уведомлений
На этом подготовка закончена и можем приступить к написанию самого слушателя для AMI.
Слушатель АМИ
Структура проекта будет следующей:
ami
├── decorator
│ └── decorator.go
├── go.mod
├── go.sum
├── main.go
└── tg
└── tg.go
Далее мы более подробно разберем, для чего нужны эти файлы:
- Создаем папку ami
mkdir ami
- Переходим в созданную папку
cd ami
- Создаем новый go модуль
go mod init
- Создаем папки и файлы, как в структуре
mkdir decorator
mkdir tg
touch decorator/decorator.go
touch tg/tg.go
Приступим к написанию функции слушателя
- Открываем файл
decorator/decorator.go
vim decorator/decorator.go
package decorator
import (
"fmt"
"net"
"strings"
)
type Handler func(string)
func Watcher(handler Handler, events ...string){
login := "Action: Login\r\nUsername: amigo\r\nSecret: admin\r\n\r\n"
conn, err := net.Dial("tcp", "127.0.0.1:5038")
checkErr(err)
defer conn.Close()
_, err = conn.Write([]byte(login))
welcome := make([]byte, 1024)
_, err = conn.Read(welcome)
checkErr(err)
fmt.Println(string(welcome))
checkErr(err)
for {
data := make([]byte, 1024)
_, err = conn.Read(data)
checkErr(err)
text := strings.Split(string(data), "\r\n")
for _, value := range text{
event := strings.Split(value, ":")
if event[0] == "Event"{
eventName := strings.TrimSpace(event[1])
for _, e := range events{
if eventName == e{
handler(string(data))
}
}
}
}
}
}
func checkErr(err error){
if err != nil{
panic(err)
}
}
- Импортируем необходимые для работы модули
import (
"fmt"
"net"
"strings"
)
- Объявляем новую функцию слушателя, которая принимает в себя handler (функция для обработки ивентов, переданных ей) и массив с ивентами, которые нужно отслеживать
func Watcher(handler Handler, events ...string)
- Функция для обработки ошибок
func checkErr(err error){
if err != nil{
panic(err)
}
}
login := «Action: Login\r\nUsername: amigo\r\nSecret: admin\r\n\r\n» — переменная, содержащая логин/пасс для AMI
conn, err := net.Dial(«tcp», «127.0.0.1:5038») — подключаемся к сокету
checkErr(err) — обработка ошибки
defer conn.Close() — указываем, что после выполнения функции нужно закрыть соединение с сокетом
_, err = conn.Write([]byte(login)) — отправляем в сокет строку с авторизацией
welcome := make([]byte, 1024) — создаем новый срез байтов
_, err = conn.Read(welcome) — читаем, что нам послал сокет и записываем в заранее созданный срез
checkErr(err) — обработка ошибки
fmt.Println(string(welcome)) — выводим в консоль полученные с сокета данные, предварительно переведя их в строку методом string()
checkErr(err) — обработка ошибки
for { // бесконечный цикл
data := make([]byte, 1024) // создаем срез байтов с названием data
_, err = conn.Read(data) // читаем то, что нам было отправлено в сокет
checkErr(err) // обрабатываем ошибки
text := strings.Split(string(data), "\r\n") // разделим полученное по переводу строки
// далее создаем бесконечный цикл, внутри которого мы будем принимать все, что нам прилетает от AMI.
for _, value := range text{ // перебираем полученный массив и записываем значения в переменную value
event := strings.Split(value, ":") // делим переменную value по : и записываем это в переменную event
if event[0] == "Event"{ // проверяем, что первый элемент event равен слову Event
eventName := strings.TrimSpace(event[1]) // удаляем пробел у второго элемента event и записываем в переменную eventName
for _, e := range events{ // перебираем массив events, который мы передали в эту функцию и записываем значения в переменную e
if eventName == e{ // проверяем, равно ли значение переменной eventName значению переменной e
handler(string(data)) // передаем данные в функцию обработчик
}
}
}
}
Проходимся по каждой строке и проверяем, если строка равна Event и названия ивента есть в том массиве, что мы передали в функцию, то вызываем handler, в который передаем все полученное сообщение целиком.
- Установим библиотеку для работы с telegram
go get -u github.com/go-telegram-bot-api/telegram-bot-api/v5
- Далее открываем tg.go
vim tg/tg.go
- Пишем туда следующий код:
package tg
import (
"github.com/Syfaro/telegram-bot-api" // импортируем библиотеку для работы с тг
)
func SendMessage(chat_id int64, text string){ // создаем функцию для отправки сообщений. Она принимает chatid, в который нужно отправлять, а так же текст, который нужно отправить
token := "abc" // вставляем токен, полученный на этапе подготовки
bot, err := tgbotapi.NewBotAPI(token) // логинемся за бота, ранее созданного
checkErr(err) // проверяем на ошибки
msg := tgbotapi.NewMessage(chat_id, text) // создаем объект сообщения
bot.Send(msg) // отправляем сообщение
}
func checkErr(err error){
if err != nil{
panic(err)
}
}
- Далее открываем файл main.go
vim main.go
package main
import (
"ami/decorator" // импортируем ранее созданный нами модуль
"ami/tg" // импортируем ранее созданный нами модуль
"strings"
)
func main(){
events := []string{"Newchannel", "PeerStatus"} // создаем массив с ивентами, которые нужно мониторить
decorator.Watcher(eventHandler, events...) // запускаем функцию слушателя и передаем туда функцию для обработки и массив с ивентами
}
func eventHandler(event string){ // сама функция обработчик, которая принимает аргументами строку
var list_of_employees = map[string]int64{"100":333333333} // создаем словарь внутренний номер - tgid для того, чтобы писать пользователю, когда на него придет звонок
var cid string // создаем переменную для хранения номера звонящего
var exten string // создаем переменную для хранения номера, на который позвонили
e := strings.Split(event, "\r\n") // разделим полученное по переводу строки
for _, value := range e{ // перебираем получившийся массив
line := strings.Split(value, ":") // делим полученное значение по :
action := line[0] // записываем первый элемент массива в переменную action
if action == "CallerIDNum"{ // проверяем равно ли значение переменной action тексту CallerIDNum
cid = strings.TrimSpace(line[1]) // удаляем пробелы у 2 элемента массива и записываем в переменную cid
}
if action == "Exten"{ // проверяем равно ли значение переменной action тексту Exten
exten = strings.TrimSpace(line[1]) // удаляем пробелы у 2 элемента массива и записываем в переменную exten
}
}
notify := "Звонок от: "+cid // создаем строку, которую отправим пользователю в тг
tg.SendMessage(list_of_employees[exten], notify) // вызываем функцию для отправки сообщения в телеграм, передавая туда тгид из словаря list_of_employees берем значения по внутреннему номеру, на который позвонили и текст
}
- Сохраняем и запускаем файл main.go
go run main.go
- Видим следующее сообщение
- Далее делаем вызов, я делаю со 101 на 100
Вывод: Данный скрипт можно использовать для интеграций. Вместо отправки уведомлений в тг можно поднимать карточку в какой-либо срм или уведомлять на почту. Описан был простой способ без каких-либо дополнительных проверок. Для более лучшей работы хорошо было бы создать service файл для скрипта и запускать его, как службу через systemctl. Более подробно о ami events можно почитать тут https://wiki.asterisk.org/wiki/display/AST/Asterisk+13+AMI+Events
Остались вопросы?
Я - Першин Артём, менеджер компании Voxlink. Хотите уточнить детали или готовы оставить заявку? Укажите номер телефона, я перезвоню в течение 3-х секунд.
категории
- DECT
- Linux
- Вспомогательный софт при работе с Asterisk
- Интеграция с CRM и другими системами
- Интеграция с другими АТС
- Использование Elastix
- Использование FreePBX
- Книга
- Мониторинг и траблшутинг
- Настройка Asterisk
- Настройка IP-телефонов
- Настройка VoIP-оборудования
- Новости и Статьи
- Подключение операторов связи
- Разработка под Asterisk
- Установка Asterisk
VoIP оборудование
ближайшие курсы
Новые статьи
10 доводов в пользу Asterisk
Распространяется бесплатно.
Asterisk – программное обеспечение с открытым исходным кодом, распространяется по лицензии GPL. Следовательно, установив один раз Asterisk вам не придется дополнительно платить за новых абонентов, подключение новых транков, расширение функционала и прочие лицензии. Это приближает стоимость владения станцией к нулю.
Безопасен в использовании.
Любое программное обеспечение может стать объектом интереса злоумышленников, в том числе телефонная станция. Однако, сам Asterisk, а также операционная система, на которой он работает, дают множество инструментов защиты от любых атак. При грамотной настройке безопасности у злоумышленников нет никаких шансов попасть на станцию.
Надежен в эксплуатации.
Время работы серверов некоторых наших клиентов исчисляется годами. Это значит, что Asterisk работает несколько лет, ему не требуются никакие перезагрузки или принудительные отключения. А еще это говорит о том, что в районе отличная ситуация с электроэнергией, но это уже не заслуга Asterisk.
Гибкий в настройке.
Зачастую возможности Asterisk ограничивает только фантазия пользователя. Ни один конструктор шаблонов не сравнится с Asterisk по гибкости настройки. Это позволяет решать с помощью Asterisk любые бизнес задачи, даже те, в которых выбор в его пользу не кажется изначально очевидным.
Имеет огромный функционал.
Во многом именно Asterisk показал какой должна быть современная телефонная станция. За многие годы развития функциональность Asterisk расширилась, а все основные возможности по-прежнему доступны бесплатно сразу после установки.
Интегрируется с любыми системами.
То, что Asterisk не умеет сам, он позволяет реализовать за счет интеграции. Это могут быть интеграции с коммерческими телефонными станциями, CRM, ERP системами, биллингом, сервисами колл-трекинга, колл-бэка и модулями статистики и аналитики.
Позволяет телефонизировать офис за считанные часы.
В нашей практике были проекты, реализованные за один рабочий день. Это значит, что утром к нам обращался клиент, а уже через несколько часов он пользовался новой IP-АТС. Безусловно, такая скорость редкость, ведь АТС – инструмент зарабатывания денег для многих компаний и спешка во внедрении не уместна. Но в случае острой необходимости Asterisk готов к быстрому старту.
Отличная масштабируемость.
Очень утомительно постоянно возвращаться к одному и тому же вопросу. Такое часто бывает в случае некачественного исполнения работ или выбора заведомо неподходящего бизнес-решения. С Asterisk точно не будет такой проблемы! Телефонная станция, построенная на Asterisk может быть масштабируема до немыслимых размеров. Главное – правильно подобрать оборудование.
Повышает управляемость бизнеса.
Asterisk дает не просто набор полезных функций, он повышает управляемость организации, качества и комфортности управления, а также увеличивает прозрачность бизнеса для руководства. Достичь этого можно, например, за счет автоматизации отчетов, подключения бота в Telegram, санкционированного доступа к станции из любой точки мира.
Снижает расходы на связь.
Связь между внутренними абонентами IP-АТС бесплатна всегда, независимо от их географического расположения. Также к Asterisk можно подключить любых операторов телефонии, в том числе GSM сим-карты и настроить маршрутизацию вызовов по наиболее выгодному тарифу. Всё это позволяет экономить с первых минут пользования станцией.