📜 ⬆️ ⬇️

Asterisk: We will provide VIP clients with the first place in the call queue, as well as we will connect the client with a specific operator for a specified time.

Asterisk is fun!
Every time I encounter a non-standard task, I am glad, I am glad of the opportunity to plunge headlong back into this wonderful state of creativity, the work of thought. Recently, such tasks appear often and it's great.
The names in the title have been implemented and are working, which means it's time to share our solutions with the community.

I'll tell you a little more about each.

1. Organize a list of phone numbers for VIP customers.
Calls from VIP-clients should get to the first place in the Asterisk queue, for the prompt processing of their calls. You also need to be able to conveniently add and remove contractors from this list.
')
2. Associate the customer's call with a specific queue operator for a specified time.
Configure Asterisk so that in his "memory", for a certain time, there is information about which of the queue operators accepted the call. The person called from the number 8913 * 75 * 5 * 0 and gets to the operator's queue Alena and needs to be done so that, for example, one day, only Alena and no one else receive incoming calls from this number.
But that's not all, if the client does not want to communicate with Alyona, he can press the * key on his phone and next time he will get to another operator.

With the introduction to this end, a little Python, MySQL and tricky dialplan are waiting for you under the cut.

List of VIP clients.
Having started the implementation, I first began to write a web-interface for working with the list of contractors, but over time I realized that it would be much longer than to find something ready. Indeed, after half an hour, I had already deployed a very convenient version of the phone book, written in php in conjunction with MySQL - just what you need.
Many thanks to the developers of PHP Address Book for their work.
I will not describe the installation - everything is trivial and very detailed in the manual attached to the project archive.
The interface is very convenient and intuitive.


After entering the necessary data, I went to write a Python script that would take Asterisk CALLERID from it, process it, make queries to the database and set the desired priority to the caller based on the results.
I got the code like this:
#!/usr/bin/env python #-*- coding: utf-8 -*- import MySQLdb,sys,re def WhatKindOfNumber(WKONnumber): if re.match(r'^[78]3843(\d{6})$', WKONnumber): # print " 11     [78]3843([78] +   ): " return WKONnumber[5:11],"our-town" if re.match(r'^[3]843{(\d{6})$', WKONnumber): # print " 10     3843(  ): " return WKONnumber[4:10],"our-town" if re.match(r'^[3](\d{6})$', WKONnumber): # print " 7     3( ,  7 ): " return WKONnumber[1:7],"our-town" if re.match(r'^(\d{6})$', WKONnumber): # print " 6 : " return WKONnumber,"our-town" if re.match(r'^\+(\d{11})$', WKONnumber): # print " 11     +7: " return WKONnumber[2:12],"mobile" if re.match(r'^[78]9(\d{9})$', WKONnumber): # print " 11     89  79: " return WKONnumber[1:11],"mobile" if re.match(r'^[9](\d{9})$', WKONnumber): # print " 10     9: " return WKONnumber,"mobile" if re.match(r'^[78][^9](\d{9})$', WKONnumber): # print " 11     7  8,   : " return WKONnumber[1:11],"another-town" if re.match(r'^[^9](\d{9})$', WKONnumber): # print " 10    : " return WKONnumber,"another-town" 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() 

In WhatKindOfNumber function, I process the received phone number, if necessary, bring it to the desired form and determine its type. Further, depending on the type, I request the data in the database and set a priority value in Asterisk - 5 if there is no number and 10 if it is.
The higher value of QUEUE_PRIO is the higher priority.

Things to do, add a line with an AGI call in front of the Queue.
Like this (I prefer dialplans on ael - do not judge):
 200601 => { &recording(${CALLERID(num)},${EXTEN}); Answer(); AGI(vip_or_not.py,${CALLERID(num)}); Queue(first_TD,tT,,,20); Hangup(); } 


With this task all, we go further.

Bundle client <-> operator (manager).
Let's take for granted that the relationship between Asterisk and MySQL is already organized (if not, you can see how to do it here ).

We go to mysql and create a table in which we will keep records of customer bindings to operators.
 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; 


Now we will enter into func_odbc.conf requests that will be executed from the dialplan.
 [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})}' 


Dialplan itself:
 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(); }; 


The logic of the dialplan is:
Determine whether the base number of the phone from which we received a call.
1. If not, then go to the last else NoOp (== IF THE NUMBER DOESN'T EXIST IN DB ==); and send the call to the queue, then assign the variable AGENT to the value of the responding operator. And insert in DB - NUMBER, DATE in UTC, AGENT.

2. If the number in the database is, then check the time. If the date in the database is less than TIMEOUT_OF_NUMBER, then we will send a call to a specific agent and update the time, if more, then to the queue.

* in order to be able to get the value of the variable MEMBERINTERFACE, you must set the parameter setinterfacevar = yes in the EACH queue config
I look like this:
 [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) ... 


Regularly delete obsolete records from the database.
I wrote this bash script:
 #!/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 

Runs on the crown, at least once a minute - depends on the value of TIMEOUT_OF_NUMBER
To be able to remove the binding, the caller needs to add this line to features.conf
 delete_number_by_client => *,peer,Macro,delnum 

and in dialplan to add such here a context
 context macro-delnum { s => { Progress(); NoOP(== CALLERID IN MACRO-DELNUM IS ${NUM_TO_DEL} ==); System(/var/lib/asterisk/agi-bin/delete_numbers_by_request.sh ${NUM_TO_DEL}); HangUP(); MacroExit(); }; }; 

and then, if at the time of the conversation the client clicks *, the binding will be removed from the table.

For the basis of this decision, I took an article - habrahabr.ru/post/204048 ,
but there the author modestly kept silent about many nuances.

Conclusion
I have a pleasant aftertaste from successfully implemented tasks, Asterisk is a whole world, and sometimes from the opportunities it opens up, my head is spinning. This is an amazing feeling when you work on something for a long time, you manage to win, and then share it with other people - you will be useful to someone.
On this I finish, love your work, good luck to you and interesting, difficult tasks!

Source: https://habr.com/ru/post/270125/


All Articles