Asterisk — это fun!
Каждый раз сталкиваясь с какой-то нестандартной задачей я радуюсь, радуюсь возможности снова погрузиться с головой в это чудесное состояние творчества, работы мысли. В последнее время такие задачи появляются часто и это здорово.
Обозначенные в заголовке были реализованы и работают, а значит пришло время поделиться с сообществом своими решениями.
Расскажу немного подробнее о каждой.
1. Организовать список номеров телефонов VIP-клиентов.
Звонки от VIP-клиентов должны попадать на первое место в очереди Asterisk, для скорейшей обработки именно их обращения. Так же нужно иметь возможность удобно добавлять и удалять контрагентов из этого списка.
2. Связать звонок клиента с конкретным оператором очереди на заданное время.
Настроить Asterisk так, чтобы в его «памяти», на какое-то заданное время, оставалась информация о том, какой из операторов очереди принял вызов. Позвонил человек с номера 8913*75*5*0 и попадает к оператору очереди Алёна и нужно сделать так, чтобы в течение, например суток, входящие звонки с этого номера принимала только Алёна и никто другой.
Но это еще не все, если клиент не хочет общаться с Аленой, то он может нажать клавишу * на своем телефоне и в следующий раз попадет уже к другому оператору.
С вступлением на этом заканчиваю, немного Python, MySQL и хитрого dialplan ждут вас под катом.
Список VIP-клиентов.
Приступив к реализации, я первым делом начал писать web-интерфейс для работы со списком контрагентов, но со временем понял, что это будет куда дольше чем найти что-то готовое. Действительно, спустя пол часа, у меня уже был развернут очень удобный вариант телефонной книги, написаной на php в связке с MySQL — как раз то, что нужно.
Большое спасибо разработчикам PHP Address Book за их труд.
Описывать установку не стану — все тривиально и очень подробно расписано в мануале, вложенном в архив проекта.
Интерфейс очень удобный и понятный
После внесения нужных данных я отправился писать Python-скрипт, который будет принимать от Asterisk CALLERID, обрабатывать его, делать запросы в БД и выставлять нужный приоритет позвонившему исходя из результатов.
Код у меня получился вот такой:
#!/usr/bin/env python #-*- coding: utf-8 -*- import MySQLdb,sys,re def WhatKindOfNumber(WKONnumber): if re.match(r'^[8]{1,1}[3]{1,1}[8]{1,1}4{1,1}[3]{1,1}(\d{6,6})$', WKONnumber): #print "Если 11 цифр и начинается с 83843(8 + код города Новокузнецк): " return WKONnumber[5:11],"our-town" if re.match(r'^[3]{1,1}[8]{1,1}4{1,1}[3]{1,1}(\d{6,6})$', WKONnumber): #print "Если 10 цифр и начинается с 3843(код города Новокузнецк): " return WKONnumber[4:10],"our-town" if re.match(r'^[3]{1,1}(\d{6,6})$', WKONnumber): #print "Если 7 цифр и начинается с 3(город Новокузнецк, только 7 цифр): " return WKONnumber[1:7],"our-town" if re.match(r'^(\d{6,6})$', WKONnumber): #print "Если 6 цифр: " return WKONnumber,"our-town" if re.match(r'^\+(\d{11,11})$', WKONnumber): #print "Если 11 цифр и начинается с +7: " return WKONnumber[2:12],"mobile" if re.match(r'^[8]{1,1}[9]{1,1}(\d{9,9})$', WKONnumber) or re.match(r'^[7]{1,1}[9]{1,1}(\d{9,9})$', WKONnumber): #print "Если 11 цифр и начинается с 89 или 79: " return WKONnumber[1:11],"mobile" if re.match(r'^[9]{1,1}(\d{9,9})$', WKONnumber): #print "Если 10 цифр и начинается с 9: " return WKONnumber,"mobile" if re.match(r'^[8]{1,1}[^9]{1,1}(\d{9,9})$', WKONnumber): #print "Если 11 цифр и начинается с 8, но не сотовый: " return WKONnumber[1:11],"another-town" if re.match(r'^[^9]{1,1}(\d{9,9})$', WKONnumber): #print "Если 10 цифр и не сотовый: " return WKONnumber,"another-town" #print "Если не нашлось совпадений, вернем как получили" return WKONnumber,"default" def agi_command(cmd): print cmd sys.stdout.flush() return sys.stdin.readline().strip() def mysqlconnect(sql): db=MySQLdb.connect(host="127.0.0.1",port=3306,user="asterisk_user",passwd="password",db="asterisk") cursor = db.cursor() cursor.execute(sql) sql = """SELECT FOUND_ROWS(); """ cursor.execute(sql) row = cursor.fetchone() db.close() return row[0] def main(): number, typeofnumber = WhatKindOfNumber(sys.argv[1]) if typeofnumber == "our-town" or typeofnumber == "default": sql = """select SQL_CALC_FOUND_ROWS * from addressbook where mobile= '""" + number + """' or home= '""" + number + """' or work= '""" + number + """' or fax='""" + number + """' limit 1;""" if typeofnumber == "mobile" or typeofnumber == "another-town": sql = """select SQL_CALC_FOUND_ROWS * from addressbook where mobile like '%""" + number + """' or home like '%""" + number + """' or work like '%""" + number + """' or fax like '%""" + number + """' or mobile like '""" + number + """' or home like '""" + number + """' or work like '""" + number + """' or fax like '""" + number + """' limit 1;""" result = mysqlconnect(sql) if result == 0: response = agi_command("EXEC Set QUEUE_PRIO=5") if result > 0: response = agi_command("EXEC Set QUEUE_PRIO=10") sys.exit(0) if __name__ == "__main__": main()
В функции WhatKindOfNumber я обрабатываю полученный номер телефона, при необходимости привожу его к нужному мне виду и определяю его тип. Далее, в зависимости от типа, запрашиваю данные в БД и выставляю в Asterisk’е значение приоритета — 5 если номера нет и 10 если он есть.
БОльшее значение QUEUE_PRIO — бОльший приоритет.
Дело за малым, добавить строчку с вызовом AGI перед Queue.
Например так(диалпланы предпочитаю на ael — не обессудьте):
200601 => { &recording(${CALLERID(num)},${EXTEN}); Answer(); AGI(vip_or_not.py,${CALLERID(num)}); Queue(first_TD,tT,,,20); Hangup(); }
С этой задачей все, идем далее.
Связка клиент <-> оператор(менеджер).
Примем как данность, что взаимосвязь Asterisk и MySQL уже организована(если нет, то можете подсмотреть как это сделать тут).
Идем в mysql и создаем табличку, в которой у нас будут храниться записи о привязках клиентов к операторам.
mysql>use asterisk; mysql> CREATE TABLE `numbers_remember` ( `id` int(9) unsigned NOT NULL auto_increment, `number` varchar(80) NOT NULL default 'NULL', `date` varchar(80), `agent` varchar(120) NOT NULL default '', PRIMARY KEY (`id`), UNIQUE KEY `ix_phone` (`number`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=UTF8; mysql> grant all on asterisk.* to 'asterisk_user'@'localhost' identified by 'password'; mysql> flush privileges;
Теперь внесем в func_odbc.conf запросы, которые будут выполняться из диалплана.
[GET_DATA] dsn=asterisk readsql=SELECT agent, date, number FROM asterisk.numbers_remember WHERE number='${ARG1}' [SET_DATA] dsn=asterisk writesql=INSERT INTO asterisk.numbers_remember (number,date,agent) VALUES ('${SQL_ESC(${VAL1})}','${SQL_ESC(${VAL2})}', '${SQL_ESC(${VAL3})}') [UPDATE_TIME] dsn=asterisk writesql=UPDATE asterisk.numbers_remember SET date='${SQL_ESC(${VAL1})}' WHERE number='${SQL_ESC(${VAL2})}' [DELETE_DATA] dsn=asterisk writesql=DELETE FROM asterisk.numbers_remember WHERE number='${SQL_ESC(${VAL1})}' AND date='${SQL_ESC(${VAL2})}'
непосредственно сам диалплан:
globals { TIMEOUT_OF_NUMBER=86400; // таймаут удержания номера в базе в секундах }; 1333 => { Set(__DYNAMIC_FEATURES=delete_number_by_client); //&recording(${CALLERID(number)},${EXTEN}); //Set(DB(clients/number)=${CALLERID(num)}); Set(__CALLFROMNUM=${CALLERID(num)}); Set(ARRAY(AGENT,DATE,NUMBER)=${ODBC_GET_DATA(${CALLERID(num)})}); if("${NUMBER}"!="") { NoOp(== IF THE NUMBER ISN'T EQUAL "NULL" ==); Set(DATERESULT=${MATH(${EPOCH}-${DATE},i)}); if(${DATERESULT}<${TIMEOUT_OF_NUMBER}) { NoOp(== IF ${DATERESULT} < ${TIMEOUT_OF_NUMBER} ==); Set(_NUM_TO_DEL=${CALLERID(NUM)}); &recording(${CALLFROMNUM},${EXTEN}); Dial(SIP/${AGENT},20,g); if("${DIALSTATUS}"!="ANSWER") { &recording(${CALLFROMNUM},${EXTEN}); Queue(Novokuznetsk,cnF); Set(AGENT=${CUT(MEMBERINTERFACE,/,2)}); Set(ODBC_DELETE_DATA()=${NUMBER},${DATE}); Set(ODBC_SET_DATA()=${CALLFROMNUM},${EPOCH},${AGENT}); }; Set(ODBC_UPDATE_TIME()=${EPOCH},${NUMBER});}; if(${DATERESULT}>${TIMEOUT_OF_NUMBER}) { NoOp(== IF ${DATERESULT} > ${TIMEOUT_OF_NUMBER} ==); Set(ODBC_DELETE_DATA()=${NUMBER},${DATE}); &recording(${CALLFROMNUM},${CALLFROMNUM}); Queue(Novokuznetsk,cnF); Set(AGENT=${CUT(MEMBERINTERFACE,/,2)}); Set(ODBC_SET_DATA()=${CALLFROMNUM},${EPOCH},${AGENT}); }; } else { NoOp(== IF THE NUMBER DOESN'T EXIST IN DB ==); &recording(${CALLFROMNUM},${EXTEN}); Queue(Novokuznetsk,cnF); Set(AGENT=${CUT(MEMBERINTERFACE,/,2)}); NoOp(${CALLFROMNUM},${EPOCH},${AGENT}); Set(ODBC_SET_DATA()=${CALLFROMNUM},${EPOCH},${AGENT}); }; HangUp(); };
Логика диалплана такая:
Определяем есть ли в базе номер телефона, с которого нам пришел вызов.
1. Если нет, то идем в последний else NoOp(== IF THE NUMBER DOESN’T EXIST IN DB ==); и отправляем звонок в очередь, после чего присваиваем переменной AGENT значение ответившего оператора. И инсертим в БД — НОМЕР, ДАТУ в UTC, АГЕНТА.
2. Если номер в БД есть, то проверяем время. Если дата в БД менее TIMEOUT_OF_NUMBER, то отправим звонок конкретному агенту и обновим время, если больше, то в очередь.
*для того, чтобы иметь возможность заполучить значение переменной MEMBERINTERFACE нужно в конфиге КАЖДОЙ очереди указать параметр setinterfacevar=yes
у меня выглядит так:
[general] persistentmembers = yes autofill = yes updatecdr=yes [StandardQueue](!) setinterfacevar=yes music=default strategy=rrmemory timeout = 12 retry = 1 timeoutpriority = conf joinempty=yes leavewhenempty=no ringinuse=yes [Novokuznetsk](StandardQueue) ... [Kemerovo](StandardQueue) ... [Mejdurechensk](StandardQueue) ...
Регулярно удаляем устаревающие записи из БД.
Я написал вот такой скрипт на bash:
#!/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin TIMEOUT_OF_NUMBER=`grep TIMEOUT_OF_NUMBER= /etc/asterisk/extensions.ael| sed s/[^0-9]//g` CURRENT_DATE=`date +%s` THRESHOLD_DATE=$(($CURRENT_DATE-$TIMEOUT_OF_NUMBER)) mysql -e "delete from numbers_remember where date<$THRESHOLD_DATE;" -uroot -p123 asterisk
Запускается по крону, хоть раз в минуту — зависит от значения TIMEOUT_OF_NUMBER
Для возможности удаления привязки позвонившим клиентом нужно добавить в features.conf вот такую строчку
delete_number_by_client => *,peer,Macro,delnum
и тогда, если в момент разговора клиент нажмет *, привязка удалится из таблицы.
За основу этого решения я взял статью — habrahabr.ru/post/204048,
но там автор скромно умолчал многие нюансы.
Заключение.
У меня осталось приятное послевкусие от успешно реализованных задач, Asterisk — это целый мир и порой от возможностей, в нем открывающихся, голова идет кругом. Это потрясающее ощущение, когда долго над чем-то работаешь, сумеешь победить, а потом еще и поделишься с другими людьми — будешь кому-то полезным.
На этом я заканчиваю, любите свой труд, удачи вам и интересных, сложных задач!
ссылка на оригинал статьи http://habrahabr.ru/post/270125/
Добавить комментарий