sip messages: отложенная доставка

Тема о сообщениях (аля SMS) в Астериске не первая на Хабре, но у всех публикаций есть один недостаток — они не обладают функционалом отложенной доставки сообщений. Когда получатель не в сети, вы получаете об этом сообщение при попытке отправки ему message, и предложение попробовать позднее.

image
Непорядок!

Работать будем с asterisk 11, с установленным FreePBX. Традиционно «без конфигов» в этот раз не получится :(

Итак, разрешаем работу messages и указываем контекст обработки оных, в разделе вебморды Settings → Asterisk SIP Settings. В самом низу добавляем кастомные поля для sip.conf и указываем:
accept_outofcall_message = yes
outofcall_message_context = messages
auth_message_requests = no

Создаем в extensions_custom.conf этот контекст:
[messages]
exten => _.,1,Set(MSG_TO=${CUT(MESSAGE(to),@,1)})
exten => _.,n,MessageSend(${MSG_TO},${MESSAGE(from)})
exten => _.,n,GotoIf($["${MESSAGE_SEND_STATUS}" != "SUCCESS"]?sendfailedmsg)
exten => _.,n,Hangup()
exten => _.,n(sendfailedmsg),Set(MSG_TMP=${CUT(MESSAGE(from),<,2)})
exten => _.,n,Set(MSG_FROM=${CUT(MSG_TMP,@,1)})
exten => _.,n,Set(ODBC_SAVE_MESSAGE("${MESSAGE(from)}","${MSG_TO}","${MESSAGE(body)}")=1)
exten => _.,n,Set(MESSAGE(body)="[${STRFTIME(${EPOCH},,%d%m%Y-%H:%M:%S)}] Ваше сообщение для ${EXTEN} не доставлено. Оно будет доставлено, когда абонент появится в сети.")
exten => _.,n,MessageSend(${MSG_FROM}, SYSTEM)
exten => _.,n,Hangup()

В этом контексте присутствует вызов ODBC-функции, которая сохраняет «SMS-ку» в СУБД MySQL. Чтобы не морочится с отдельными базами и DSN, я создал таблицу в имеющейся базе asteriskcdrdb:

CREATE TABLE IF NOT EXISTS `messages` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `dt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `mfrom` varchar(100) CHARACTER SET utf8 NOT NULL,
  `mto` varchar(100) CHARACTER SET utf8 NOT NULL,
  `mbody` text CHARACTER SET utf8 NOT NULL,
  `delivered` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

В файле func_odbc.conf добавим саму ODBC_функцию:
[SAVE_MESSAGE]
writesql = INSERT INTO messages (mfrom,mto,mbody) VALUES ('${ARG1}','${ARG2}','${BASE64_ENCODE(${ARG3})}')
dsn = asteriskcdrdb

Как видим, текст сообщения перед сохранением кодируется в base_64. Таким нехитрым образом я обхожу глюки с кириллицей. Кстати, передачу текста в контексте messages обязательно заключать в кавычки, иначе при появлении например запятой в тексте диалпан считает это разделителем параметров :)

Итак, сообщения у нас сохраняются в базу при отсутствии абонента в сети. Осталось настроить механизм доставки ему сего сообщения. Делать будем на php, скрипт я положил в /etc/asterisk/send_delayes_messages.php:

В качестве метки для факта доставки я использую поле delivered типа timestamp, если там нули — то сообщение нуждается в доставке. Таким образом, пробегая по сохраненным недоставленным сообщениям, мы проверям по каждому наличие регистрации пира через команду cli, и если он в сети — создаем outgoing call file, который и производит доставку сего сообщения. После этого скрипт помечает в базе сообщение, устанавливая дату отправки.

Останется прикрутить скрипт через php -f /etc/asterisk/send_delayes_messages.php в поминутный крон и раз в минуту будет производится проверка и попытка доставки сообщения.

Какие минусы у этой реализации? Первый — регистрация статуса пира держится какое то время после обрыва, и вполне возможна ситуация, когда пир кратковременно зарегистрируется и отвалится, а система «отправит» в течение минуты ему сообщение, и будет считать его доставленным. Выкрутится можно, использовав не Application в call-файле, а передачу данных в контекст с проверкой статуса переменной ${MESSAGE_SEND_STATUS}. Наверное, возможно будет использовать имеющийся контекст, задав переменные через Set в call-файле.
Но я пока остановился на этом: некогда.

Удачи!

Комментарии (0)

© Habrahabr.ru