📜 ⬆️ ⬇️

Record incoming calls

A few months ago, my friend asked for help in resolving the issue of recording incoming calls. All necessary or was available, or promised to provide.

image

If it is interesting, my experience of implementation on python along with the code under the cut.

Familiar provides technical support and maintenance of computer equipment. The nature of the work of employees - traveling. There is no separate dispatcher, all calls are received by the employees themselves in order to save money. There are situations when an employee cannot answer the call (on the road, in dialogue with the client) or the employee receives complaints from the client (did not do everything or not what they asked for). Such situations should be "resolved." In general, he had to somehow centralize the reception of calls, not taking on the work of the dispatcher.
')
For a friend, all incidents and changes are recorded and managed in accordance with the requirements of ITIL . Automated processes using easla.com. Lacked only calls.

Task


There was no talk about a full-fledged call-center, because analysis of calls is carried out "after the fact". Therefore, the requirements were simple:



The call object and all the necessary attributes have already been created in the database provided in easla.com. Spoke only statuses. Added the status “Impossible” in case the money ran out on the phone's account.

Decision


First of all, they agreed that they would still have to spend money on acquiring a city number and raising the asterisk server to handle incoming calls. The number was purchased from a local IP telephony provider, and the asterisk server was picked up on a separate virtual server using existing hardware. On the asterisk server, I set up forwarding of all incoming calls to the cellular assistant, thus making it a dispatcher.

It was decided to use Twisted as a FastAGI server, which would receive information about the completed call and send the information to easla.com via SOAP. All procedures are described in the system administration manual .

The conversation is recorded using the MixMonitor command , using the $ {UNIQUEID} variable as the file name.
At the end of the conversation, stop the recording of the conversation and transfer control to FastAGI server:
exten => h,1,StopMixMonitor
exten => h,n,AGI(agi://127.0.0.1:4573)

To implement the FastAGI protocol, I used the starpy library. Information about the duration of the call is obtained through CDR-records. After receiving all the necessary information in a separate stream, write it to easla.com.

Get call information
 def fastAgiMain( agi ): sequence = fastagi.InSequence() #     . cdr_vars = { 'CDR(start)':'', 'CDR(disposition)}':'', 'CDR(duration)':'', 'CDR(end)':'', 'DIALSTATUS':'', } #         easla.com    sequence.append(sendCDR, None, agi, cdr_vars, iter(cdr_vars)) #     asterisk sequence.append(agi.finish) def onFailure( reason ): log.error( "Failure: %s", reason.getTraceback()) agi.finish() return sequence().addErrback( onFailure ) #  ,       cdr_vars def sendCDR(result, agi, cdr_vars, keys): def setVar(result, key): cdr_vars[key] = result def notAvailable(reason, key): print "key " + key + " not found" try: key = keys.next() except StopIteration, err: duration = str(timedelta(seconds=int(cdr_vars['CDR(duration)']))) #   getVariable   callerid  uniqueid caller_id = agi.variables['agi_callerid'] wav_file = '/data/wav/' + agi.variables['agi_uniqueid'] + '.wav' status = cdr_vars['DIALSTATUS'] #        thread = Thread(target=sendCallInfo, args=(caller_id, duration, wav_file, status)) thread.start() return None else: return agi.getVariable(key) \ #   key .addCallback(setVar, key) \ #    cdr_vars .addErrback(notAvailable, key) \ #       key .addCallback(sendCDR, agi, cdr_vars, keys) #     



After the call control is returned to asterisk, you can convert wav to mp3 and send information to easla.com. Here it is necessary to explain why we do not use MixMonitor to convert, as suggested in many manuals. MixMonitor launches third-party applications by a separate process and does not inform FastAGI in any way that the application has been executed, and it can easily happen that by the time the call information is sent, there is no access to the mp3 file. The pydub library is used for conversion , and suds is used as the SOAP client.

We send
 def sendCallInfo(callid, callduration, wav_file ,status): raw_params = { 'incoming_call_number': callid, 'incoming_call_time': callduration,} if status: if status == 'ANSWER': raw_params['status'] = 'incoming_call_answered' if status == 'BUSY': raw_params['status'] = 'incoming_call_busy' if status == 'NOANSWER': raw_params['status'] = 'incoming_call_unanswered' if status == 'CANCEL': raw_params['status'] = 'incoming_call_unanswered' if status == 'CONGESTION': raw_params['status'] = 'incoming_call_congestion' url = 'http://easla.com/user/soap' client = Client(url) client.service.login('login','password') call_management_proc = client.service.getProcess('call_management') incoming_call_def = client.service.getObjectdef(call_management_proc, 'incoming_call', 0) keyval_array = client.factory.create('KeyValuesPairSoapArray') #   KeyValuesPairSoapArray    easla.com for key, value in raw_params.iteritems(): keyval = client.factory.create('KeyValuesPairSoap') keyval.key = key keyval.values.item.append(value) keyval_array.item.append(keyval) #      easla.com incoming_call_obj = client.service.createObjectref(incoming_call_def, None, keyval_array) if os.path.exists(wav_file): # asterisk      while is_locked(wav_file): time.sleep(1) mp3_file = wav2mp3(wav_file) with open(mp3_file, "rb") as image_file: encoded_string = base64.b64encode(image_file.read()) if encoded_string: #       mp3  file_attr = client.factory.create('KeyValuePairSoapArray') file_name = client.factory.create('KeyValuePairSoap') file_content = client.factory.create('KeyValuePairSoap') file_name.key = 'srcname' file_name.value = os.path.basename(mp3_file) file_content.key = 'content' file_content.value = encoded_string file_attr.item.append(file_name) file_attr.item.append(file_content) #     client.service.addFile(incoming_call_obj, 'incoming_call_file', file_attr) if wav_file: os.remove(wav_file) if mp3_file: os.remove(mp3_file) 



The module was able to run in the first week of operation. At first there were not enough funds on the account, and successfully checked the registration of calls with the status “Impossible”. Then the account replenished and checked the registration of calls with the other statuses.
The registry of incoming calls looks something like this:



After the start of call registration, it was possible to add another function to identify the subscriber in the registered call and create an incident based on the registered call.

If such a solution is useful to someone, I will be glad.

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


All Articles