📜 ⬆️ ⬇️

Freeswitch Phonebook



Introduction


It so happens that Freeswitch is my little weakness. Yes, Asterisk is more common in the world now, and I also know it pretty well, but ... It is Freeswitch that attracts me with something. It can be a set of possibilities, it can be super-stable, it can be a small resource consumption and a more logical device. Or maybe I'm just a hipster from the IT world and I want to be not like everyone else. How to know. We are not psychologists to dig into the darkness of the human soul, so we just take it for granted and deal with things more practical. We will improve customer service. On the agenda is improved customer personalization through recognition.

Trivia


The phone book is an extremely utilitarian thing. Present in all more or less decent add-on IP-telephony systems. Allows for incoming calls to identify the customer by phone number. Of course, we are not a sales department, we don’t write to everyone in CRM, but even an employee of a customer who calls technical support with their problems is pleased to be recognized right away. At the application level, the phone book is usually connected as a plug-in, allowing you to use a certain information store, most often a database, as a data source. Not escaped a similar fate and Freeswitch. At different times, we used two phonebook options:
')

I will tell about the implementation of both options. Each has its own advantages and disadvantages.

The phone book based on the database is simpler in the device and more standard. On the official website of Freeswitch there is a good dock, for its installation and configuration. But since there is no web interface on the telephony server (I’m a principled opponent of web interfaces to telephone exchanges, but this is a completely different story, some time next), then a limited number of unlimited people who are not afraid of the word base can tune the phone book data , query and others like them. And then the console , as in my case. You ask, why not MySQL? Because it was not necessary. We do not have all of Russia in the phone book, but only about 500 contacts. For this, SQLite turned out to be behind the eyes. The second difficulty was the process of adding information. It is necessary to add to several tables, take into account the connection and do not forget about duplicates. I still have a document somewhere in the order of fulfilling requests to add and update information in the phone book. The advantages are obvious - the database engine does not require the installation and integration of additional software and is in itself as simple as mooing. The entire phone book is in one file, easily backed up and saved. The tables inside are extremely simple and understanding their structure does not require a special thought process. It is enough to raise the stick heavier and knock the neighbor to the left. Let him think yes.

The second version of the phone book was less trivial in implementation. Since we are now actively trying to master CMDB iTop, the idea has appeared to cross the phone book with it. Fortunately, the standard contact storage module in the CMDB is present and corresponds. Standard methods failed to do this. Lua came to the rescue, everything turned out clean and tidy with her. Among the advantages of this approach, it is possible to note the presence of a convenient interface for adding and editing contacts, the ability to save any data in the CMDB, transfer it to the telephone exchange and use it when routing a call (not yet used, but planned). Among the shortcomings, of course, are the transmission delays over the network, dependence on external service, the need for programming and adjustment of this entire enterprise. But the result is worth it. And, by the way, I do not persuade you to use just such an option, just by his example I would like to show how to organize interaction with external services.

Piece of cake


According to the old IT tradition, let's go from simple to complex. Let's start with the fact that we lay down on the sofa and do procrastination. See how easy it is. But the work is not a wolf, you will not kick the legs, and therefore we move the body to a vertical position and move closer to the telephone station console ...
The phonebook module in Freeswitch is called mod_cidlookup Description on the old site Description on the new site . First you need to decide whether the module is with us. Who knows, suddenly leaked out imperceptibly.

We look at the presence of a module in the modules folder.

ls -la /usr/local/freeswitch/mod/ | grep mod_cidlookup 

If there are no files, then I have bad news for you. The module must be assembled or installed. I prefer to build Freeswitch myself, so the source tree ready for building is always at hand. A description of how to assemble a module for Freeswitch is beyond the scope of this article, so leave it there and just accept that the module already lies in the folder we need. Now it needs to be configured and prepared for it. The main configuration of the module settings is located in the file.

/usr/local/freeswitch/conf/autoload_configs/cidlookup.conf.xml
The minimum working config looks like this.

Working config
 <configuration name="cidlookup.conf" description="cidlookup Configuration"> <settings> <!--   . --> <param name="cache" value="true"/> <!--      - . --> <param name="cache-expire" value="86400"/> <!--  .    3 (, !)    .   ,    ,  .    ,     . --> <param name="odbc-dsn" value="sqlite:///usr/local/freeswitch/db/phonebook.db"/> <!--    .      .      . --> <param name="sql" value=" SELECT (name || (CASE WHEN comment != '' THEN ' (' || comment || ')' ELSE '' END)) AS name FROM numbers n JOIN phonebook p ON n.pid = p.id WHERE n.number='${caller_id_number}' LIMIT 1 "/> </settings> </configuration> 


Notice the conditional expression in SELECT. It allows you to extract the client’s company from the database and its position from the comment field. If the field is empty, then only the last name and first name are returned, as it should be. Due to the fact that only one line is always returned, duplicates are ignored, and the earliest entry from the phone book is returned. Therefore, before adding data, it is necessary to check that they are not in the database. Linking in the phone book is used to support multiple phone numbers for one user.

We proceed to create a database for our phone book.

 sudo -u freeswitch sqlite3 /usr/local/freeswitch/db/phonebook.db 

 --     . -- . CREATE TABLE phonebook ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE, name VARCHAR (128) NOT NULL, comment VARCHAR (256) ); CREATE INDEX name ON phonebook (name); --  . CREATE TABLE numbers ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, pid INTEGER REFERENCES phonebook (id) ON DELETE CASCADE NOT NULL, NUMBER VARCHAR (32) NOT NULL ); CREATE INDEX NUMBER ON numbers (NUMBER); --     . INSERT INTO `phonebook` (`name`, `comment`) VALUES (' ', '  , '); INSERT INTO `numbers` (`pid`, `number`) VALUES (LAST_INSERT_ROWID(), '79152323245'); INSERT INTO `phonebook` (`name`) VALUES (' '); INSERT INTO `numbers` (`pid`, `number`) VALUES (LAST_INSERT_ROWID(), '74953462323'); 

We leave from the console SQLite.

 .exit 

Now you need to load the module and set it on the newly created phone book. Go to the console Freeswitch.

 fs_cli 

We are looking for a loaded phone book module.

 freeswitch@internal> show modules mod_cidlookup 0 total. 

Yeah, there is no module. We try to download it.

 freeswitch@internal> load mod_cidlookup +OK Reloading XML +OK 2016-10-29 14:36:33.890548 [DEBUG] mod_cidlookup.c:122 Connecting to dsn: sqlite:///usr/local/freeswitch/db/phonebook.db 2016-10-29 14:36:33.890548 [INFO] mod_enum.c:880 ENUM Reloaded 2016-10-29 14:36:33.890548 [INFO] switch_time.c:1415 Timezone reloaded 1781 definitions 2016-10-29 14:36:33.890548 [CONSOLE] switch_loadable_module.c:1538 Successfully Loaded [mod_cidlookup] 2016-10-29 14:36:33.890548 [NOTICE] switch_loadable_module.c:292 Adding Application 'cidlookup' 2016-10-29 14:36:33.890548 [NOTICE] switch_loadable_module.c:338 Adding API Function 'cidlookup' . 

Ok, the module is loaded. If you have problems connecting to the database, they will appear in the log. We are looking for it again among the loaded modules.

 freeswitch@internal> show modules mod_cidlookup type,name,ikey,filename api,cidlookup,mod_cidlookup,/usr/local/freeswitch/mod/mod_cidlookup.so application,cidlookup,mod_cidlookup,/usr/local/freeswitch/mod/mod_cidlookup.so 2 total. 

Now you need to test the performance of the module. This can also be done via the console using the cidlookup API function. We look at its syntax.

 freeswitch@internal> show api cidlookup name,description,syntax,ikey cidlookup,cidlookup API,cidlookup status|number [skipurl] [skipcitystate] [verbose],mod_cidlookup 1 total. 

We recall the data that we added to the phone book and test the work of the API.

 freeswitch@internal> cidlookup 79152323245   (  , ) freeswitch@internal> cidlookup 74953462323   

As you can see, the function returns the correct contact data from the phone book. Moreover, if there is a comment, it is also returned in brackets. Now we need to add this function to the dialplan in order to bring light and joy to people. Since the dialplan is arranged differently for everyone, I will show only its part, which concerns the phone book and I will indicate its approximate location. I have it located in the /usr/local/freeswitch/conf/dialplan/public.xml file.

 <!--     . --> <!--       . --> <extension name="cid_number_cleanup" continue="true"> <condition field="caller_id_number" expression="^(\d+)$"> <action application="set" data="effective_caller_id_number=$1" inline="true"/> </condition> </extension> <!--       . --> <extension name="cid_name_cleanup" continue="true"> <condition field="caller_id_name" expression="^(\d+)$"> <action application="set" data="effective_caller_id_name=$1" inline="true"/> </condition> </extension> <!--       ,       ,   ,    .           . --> <extension name="cid_lookup" continue="true"> <condition field="${module_exists(mod_cidlookup)}" expression="true"/> <condition field="caller_id_name" expression="^(\d+)$|^$"/> <condition field="caller_id_number" expression="^(\d+)$"> <action application="cidlookup" data="$1"/> </condition> </extension> 

Save the file, reload the dialplan with the command

 fs_cli -x reloadxml 

If there are no errors, you can check the operation of the module. After playing enough, we add the module to autoload. This is done in the /usr/local/freeswitch/conf/autoload_configs/modules.conf.xml file. We are looking for a line there

 <load module="mod_cidlookup"/> 

and uncomment it. If it is missing, then simply add it to the end of the file.

That's it, the phone book is set up. But admins would not be admins if they didn't want to constantly improve something. And the hike along this road leads to the next part of our story.

Let's rock


The interaction of Freeswitch and iTop is arranged in a completely standard way, through the REST interface described on the official website . What is the difficulty? In that it will not work directly with him, it is necessary to call for help the power of the swan plug-ins. It was originally planned to use mod_curl , but somehow it did not work out with it. It was decided to write my own small Lua script and call it via mod_lua . First of all, you need to prepare the phone book itself, that is, in one way or another, upload contacts to iTop. Your choice - sources of synchronization, download CSV or filling the phone book manually. Since we have a sandbox here and we don’t need a lot of contacts, let's create one organization and add several contacts to it. To make changes to the CMDB, you must have the appropriate rights or be an administrator.













.



Similarly, add a few more contacts. Now we have where to look and you can proceed to setting up a telephone exchange. As I said, we will request via iTop REST interface. His listing and thoughtful taste resulted in the next request.

 { "operation": "core/get", "class": "Person", "key": "SELECT Person AS P WHERE P.phone = '79101001122' OR P.add_phone = '79101001122' OR P.add_phone_2 = '79101001122' OR P.mobile_phone = '79101001122' OR P.add_mobile_phone = '79101001122'", "output_fields": "friendlyname,org_id_friendlyname,function" } 

Let's try to do it with Curl.

 curl -XPOST 'https://<itop_address>/webservices/rest.php?version=1.0' -d 'auth_user=admin&auth_pwd=password&json_data=%7B%22operation%22%3A%22core%2Fget%22%2C%22class%22%3A%22Person%22%2C%22key%22%3A%22SELECT%20Person%20AS%20P%20WHERE%20P.phone%20%3D%20%2779101001122%27%20OR%20P.add_phone%20%3D%20%2779101001122%27%20OR%20P.add_phone_2%20%3D%20%2779101001122%27%20OR%20P.mobile_phone%20%3D%20%2779101001122%27%20OR%20P.add_mobile_phone%20%3D%20%2779101001122%27%22%2C%22output_fields%22%3A%22friendlyname%2Corg_id_friendlyname%2Cfunction%22%7D' 

As you can see, the request requires authentication. The user in iTop must exist and have the necessary rights to execute requests.

In response, we get the found user:

 { "objects": { "Person::486": { "code": 0, "message": "", "class": "Person", "key": "486", "fields": { "friendlyname": "\u0412\u0430\u0441\u0438\u043b\u0438\u0439 \u041f\u0443\u043f\u043a\u0435\u0432\u0438\u0447", "org_id_friendlyname": "\u0420\u043e\u0433\u0430 \u0438 \u043a\u043e\u043f\u044b\u0442\u0430", "function": "\u0421\u0443\u043f\u0435\u0440\u0445\u043e\u043c\u044f\u043a" } } }, "code": 0, "message": "Found: 1" } 

In a more readable version

 { "objects": { "Person::486": { "code": 0, "message": "", "class": "Person", "key": "486", "fields": { "friendlyname": " ", "org_id_friendlyname": "  ", "function": "" } } }, "code": 0, "message": "Found: 1" } 

So, the data is received and transmitted, users are searched. It's time to uncover your favorite IDE and write something inspired. Create a separate folder for Freeswitch scripts.

 mkdir /usr/local/freeswitch/scripts 

In this folder we create a subfolder lib in which we will store shared libraries.

 mkdir /usr/local/freeswitch/scripts/lib 

We need standard libraries for working with sockets and SSL.
 sudo apt-get install lua-socket lua-sec 

Also useful library for encoding and decoding JSON. I chose the library http://regex.info/blog/lua/json . Download it and place it in the appropriate folder.
 wget http://regex.info/code/JSON.lua -O lib/json.lua 

And now everything is ready for our code to come to this world. In the scripts folder, create the file cidlookup.lua and use the hard keyboard to enter the following code there:

/usr/local/freeswitch/scripts/cidlookup.lua
 --    iTop. local itop_addr = 'https://<itop_address:port>'; local itop_user = 'admin'; local itop_pass = 'password'; --  . local json = (loadfile '/usr/local/freeswitch/scripts/lib/json.lua')(); --    . --[[      .               Freeswitch.            .  . ,  ,      .       Lua,    .  -. ]]-- -- local phone = arg[1]; local phone = argv[1]; --  . local command = { operation = 'core/get', class = 'Person', key = 'SELECT Person AS P WHERE P.phone = "' .. phone .. '" OR P.add_phone = "' .. phone .. '" OR P.add_phone_2 = "' .. phone .. '" OR P.mobile_phone = "' .. phone .. '" OR P.add_mobile_phone = "' .. phone .. '"', output_fields = 'friendlyname,org_id_friendlyname,function' }; --   iTop. local http = require 'socket.http'; local https = require 'ssl.https'; local ltn12 = require 'ltn12'; local request = 'auth_user=' .. itop_user .. '&auth_pwd=' .. itop_pass .. '&json_data=' .. json:encode(command); local respbody = {}; --  ,     ,    iTop  -   . --      - ,  . http.TIMEOUT = 3; local body, code, headers, status = https.request { protocol = 'tlsv1', method = 'POST', url = 'https://' .. itop_addr .. '/webservices/rest.php?version=1.0', source = ltn12.source.string(request), headers = { ["Accept"] = "*/*", ["Accept-Encoding"] = "gzip, deflate", ["Accept-Language"] = "en-us", ["Content-Type"] = "application/x-www-form-urlencoded", ["content-length"] = string.len(request) }, sink = ltn12.sink.table(respbody) }; --    ,    . caller_id = phone; --  JSON. local response = json:decode(tostring(table.concat(respbody))); -- ,   - . if(not((response == nil) or (response["objects"] == nil))) then local index = next(response["objects"]); local contact = response.objects[index]["fields"]; --   . caller_id = contact.friendlyname .. "(".. contact.org_id_friendlyname .. (not(contact["function"] == "") and ", " .. contact["function"] or "") .. ")"; end --[[             Freeswitch.             .  . ]]-- stream:write(caller_id); -- io.write(caller_id .. "\n"); 


It is time to test our creation in battle. We edit the script for its work from the command line and check for operability.

 lua cidlookup.lua 79101001122  (  , ) 

Great, everything works as it should. We edit the script, disabling work from the command line. Now connect the script to the Freeswitch. First you need to make sure that we have the appropriate module. Go to the console Freeswitch.

 fs_cli 

Check the load module Lua.

 freeswitch@internal> show modules mod_lua type,name,ikey,filename api,lua,mod_lua,/usr/local/freeswitch/mod/mod_lua.so api,luarun,mod_lua,/usr/local/freeswitch/mod/mod_lua.so application,lua,mod_lua,/usr/local/freeswitch/mod/mod_lua.so dialplan,LUA,mod_lua,/usr/local/freeswitch/mod/mod_lua.so 4 total. 

If you see these lines, then everything is in order, the module is loaded. If they are not there, the module may be assembled, but not launched. We are trying to run it.

 freeswitch@internal> load mod_lua 

If Freeswitch cannot find a module to run, then it will be necessary to assemble and upgrade it. We will assume that you have successfully solved this problem and loaded the module. We try to execute our script:

 freeswitch@internal> lua cidlookup.lua 79101001122  (  , ) 

Well, as you can see, the script works successfully and gets everything it needs. It remains to add it to the dialplan. As in the first part of the article, I will not tell you where to put it, just bring the part of the dialplan responsible for its work.

 <!--     . --> <!--       . --> <extension name="cid_number_cleanup" continue="true"> <condition field="caller_id_number" expression="^(\d+)$"> <action application="set" data="effective_caller_id_number=$1" inline="true"/> </condition> </extension> <!--       . --> <extension name="cid_name_cleanup" continue="true"> <condition field="caller_id_name" expression="^(\d+)$"> <action application="set" data="effective_caller_id_name=$1" inline="true"/> </condition> </extension> <!--       ,       ,       .           . --> <extension name="cid_lookup" continue="true"> <condition field="${module_exists(mod_lua)}" expression="true"/> <condition field="caller_id_name" expression="^(\d+)$|^$"/> <condition field="caller_id_number" expression="^(\d+)$"> <action application="set" data="effective_caller_id_name=${lua(cidlookup.lua ${caller_id_name})}"/> </condition> </extension> 

We reload the dialplan with the command

 fs_cli -x reloadxml 

And enjoy the result.

Taking out


Yes, I know, you can improve and improve. You can connect with mod_curl and simplify the code, you can finish and optimize the script, I do not argue with that. There is no limit to perfection. But any road begins with the first step, and if this article is useful to someone in his difficult work, it means that it was not written in vain. =) So I wish you health.

PS I hope someone will come in handy. I will be glad to your comments.

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


All Articles