#!/usr/bin/env python2 # coding: utf8 import sys import os import tempfile import re from asterisk import agi import logging from optparse import OptionParser from picotts import PicoTTS from helpers import playback, enable_simulate_mode, get_var, set_var, hangup, check_answered import traceback default_logfile = '/var/log/asterisk/conference.agi.log' default_lang = 'fr-FR' default_intkey = "any" default_result_varname = "CONFID" default_speed = 1.1 default_cachedir = '/var/cache/asterisk/picotts' default_read_timeout = 3000 default_read_maxdigits = 20 default_read_maxtry = 3 ####### # RUN # ####### # Options parser parser = OptionParser() parser.add_option('-d', '--debug', action="store_true", dest="debug", help="Enable debug mode") parser.add_option('-v', '--verbose', action="store_true", dest="verbose", help="Enable verbose mode") parser.add_option('--simulate', action="store_true", dest="simulate", help="Simulate AGI mode") parser.add_option('--simulate-play', action="store_true", dest="simulate_play", help="Simulate mode : play file using mplayer") parser.add_option('-t', '--read-timeout', action="store", type="int", dest="read_timeout", default=default_read_timeout, help="Read timeout in ms (Default : %i)" % default_read_timeout) parser.add_option('-m', '--read-max-digits', action="store", type="int", dest="read_maxdigits", default=default_read_maxdigits, help="Read max digits (Default : %i)" % default_read_maxdigits) parser.add_option('-T', '--read-max-try', action="store", type="int", dest="read_maxtry", default=default_read_maxtry, help="Read max try (Default : %i)" % default_read_maxtry) parser.add_option('--can-create', action="store_true", dest="can_create", help="User can create a conference") parser.add_option('-n', '--name', action="store", type="string", dest="varname", default=default_result_varname, help="User input result variable name (Default : %s)" % default_result_varname) parser.add_option('-L', '--log-file', action="store", type="string", dest="logfile", default=default_logfile, help="pico2wave path (Default : %s)" % default_logfile) parser.add_option('-l', '--lang', action="store", type="string", dest="lang", default=default_lang, help="Language (Default : %s)" % default_lang) parser.add_option('-i', '--intkey', action="store", type="string", dest="intkey", default=default_intkey, help="Interrupt key(s) (Default : Any)") parser.add_option('-s', '--speed', action="store", type="float", dest="speed", default=default_speed, help="Speed factor (Default : %i)" % default_speed) parser.add_option('-S', '--sample-rate', action="store", type="int", dest="samplerate", help="Sample rate (Default : auto-detect)") parser.add_option('-c', '--cache', action="store_true", dest="cache", help="Enable cache") parser.add_option('-C', '--cache-dir', action="store", type="string", dest="cachedir", default=default_cachedir, help="Cache directory path (Default : %s)" % default_cachedir) parser.add_option('--sox-path', action="store", type="string", dest="sox_path", help="sox path (Default : auto-detec in PATH)") parser.add_option('--pico2wave-path', action="store", type="string", dest="pico2wave_path", help="pico2wave path (Default : auto-detec in PATH)") (options, args) = parser.parse_args() # Enable logs logformat = '%(levelname)s - %(message)s' if options.simulate: logging.basicConfig(format=logformat, level=logging.DEBUG) enable_simulate_mode() else: if options.debug: loglevel = logging.DEBUG elif options.verbose: loglevel = logging.INFO else: loglevel = logging.WARNING logging.basicConfig(filename=options.logfile, level=loglevel, format=logformat) # Valid intkey parameter if options.intkey != "any" and not re.match('^[0-9#*]*$', options.intkey): logging.warning('Invalid interrupt key(s) provided ("%s"), use any.' % options.intkey) options.intkey = "any" if options.speed <= 0: logging.warning('Invalid speed provided, use default') options.speed=default_speed logging.debug('Call parameters (lang = {lang}, intkey = {intkey}, speed = {speed} and varname :\n{varname}'.format( lang=options.lang, intkey=options.intkey, speed=options.speed, varname=options.varname) ) ############# # Functions # ############# def check_confid(confid): if re.match('^[0-9]{1,4}$', confid): return True return False def set_return(result): set_var(asterisk_agi, options.varname, result) def clean_tmp(): if 'picotts' in globals(): global picotts picotts.clean_tmp() def play_msg(msg, read=False, max_digits=False): global asterisk_agi, options filepath = picotts.getAudioFile(msg) if not max_digits: max_digits = options.read_maxdigits # Playback message logging.debug('Play file %s' % filepath) result = playback(asterisk_agi, filepath, simulate_play=options.simulate_play, intkey=options.intkey, read=read, read_timeout=options.read_timeout, read_maxdigits=max_digits) return result def play_msg_and_hangup(msg): global asterisk_agi play_msg(msg) clean_tmp() set_return(None) hangup(asterisk_agi) sys.exit(0) try: picotts_args = {} # Start Asterisk AGI client if not options.simulate: asterisk_agi = agi.AGI() picotts_args['asterisk_agi'] = asterisk_agi else: asterisk_agi = None if options.cache: picotts_args['cachedir']=options.cachedir # Start PicoTTS engine picotts = PicoTTS(lang = options.lang, speed=options.speed, **picotts_args) # Check call is answered check_answered(asterisk_agi) authorized = False confid = None start = True while not authorized: nb_confid_try =0 while not confid: if nb_confid_try >= options.read_maxtry: play_msg_and_hangup(u"Vous avez atteint le nombre maximum d'essai. Peut-être avez un problème avec votre clavier de téléphone ? Nous en sommes désolé. Au revoir.") msg = u"Merci de saisir votre numéro de conférence en terminant par la touche dièse." if start: start = False msg = u"Bonjour et bienvenue sur le service de conférence téléphonique. " + msg confid = play_msg(msg, read=True) if not check_confid(confid): confid = None play_msg(u"Ce numéro de conférence est invalide. Il doit comporté entre 1 et 4 chiffres.") nb_confid_try += 1 logging.info('User choice conference %s' % confid) # Check number of current calls in this conference nb_calls = int(get_var(asterisk_agi, '${GROUP_COUNT(%s@conference)}' % confid)) logging.debug('Nb current calls in conference %s : %s' % (confid, nb_calls)) if nb_calls > 0: logging.info('Conference %s already exist' % confid) # Check PIN pin = get_var(asterisk_agi, '${DB(conf/%s/pin)}' % confid) logging.info('Current PIN of conference %s : "%s"' % (confid, pin)) if pin: nb_pin_try = 0 while not authorized: if nb_pin_try >= options.read_maxtry: play_msg_and_hangup(u"Vous avez atteint le nombre maximum d'essai. Merci de vérifier le mot de passe d'accès à la conférence auprès de l'organisateur avant de rééssayer. Au revoir.") if nb_pin_try == 0: check_pin = play_msg(u"Cette conférence est protégé par un mot de passe. Merci de le saisir en terminant par la touche dièse.", read=True) else: check_pin = play_msg(u"Merci de saisir le mot de passe d'accès de la conférence en terminant par la touche dièse.", read=True) if check_pin != pin: play_msg(u"Le mot de passe saisi est invalide.") nb_pin_try += 1 else: authorized = True else: authorized = True set_var(asterisk_agi, 'CONFBRIDGE(user,admin)', 'no') set_var(asterisk_agi, 'CONF_CREATOR', 'no') elif options.can_create: logging.info('Conference %s does not exist.' % confid) choice = play_msg(u"Cette conférence n'existe pas. Pour la créer, appuyer sur la touche 1, sinon, merci de patienter ou d'appuyer sur une autre touche pour saisir un autre numéro de conférence.", read=True, max_digits=1) if choice != "1": confid = None continue pin = None nb_pin_try = 0 while not pin: if nb_pin_try >= options.read_maxtry: play_msg_and_hangup(u"Vous avez atteint le nombre maximum d'essai. Vous avez peut-être un problème avec le clavier de votre téléphone. Nous en sommes désolé. Au revoir") if nb_pin_try == 0: pin = play_msg(u"Si vous souhaitez protéger votre conférence par un mot de passe, merci de le saisir en terminant par la touche dièse. Sinon, merci de patienter ou d'appuyer sur le touche dièse.", read=True) else: pin = play_msg(u"Merci de saisir un nouveau mot de passe en terminant par la touche dièse. Si vous ne souhaitez finalement pas protéger votre conférence, merci de patienter ou d'appuyer sur la touche dièse.", read=True) if pin: verif_pin = play_msg(u"Merci de confirmer le mot de passe de votre conférence en terminant par la touche dièse.", read=True) if verif_pin != pin: play_msg(u"Les mots de passe saisies ne correspondent pas.") nb_pin_try += 1 pin = None continue logging.info('Conference %s created with PIN "%s"' % (confid, pin)) set_var(asterisk_agi, 'DB(conf/%s/pin)' % confid, pin) else: logging.info('Conference %s created without PIN' % confid) set_var(asterisk_agi, 'DB(conf/%s/pin)' % confid, "") break play_msg(u"Votre conférence a été créé. Vous pouvez désormais communiquer son numéro à vos invités, à savoir, le numéro %s." % confid) if pin: play_msg(u"N'oubliez pas de leur communiquer également le mot de passe d'accès.") set_var(asterisk_agi, 'CONFBRIDGE(user,admin)', 'yes') set_var(asterisk_agi, 'CONF_CREATOR', 'yes') authorized = True else: logging.info('Conference %s does not exist and user can not create it.' % confid) choice = play_msg(u"La conférence numéro %s n'existe pas ou n'a pas encore commencé. Si vous pensez avoir fait une erreur, appuyer sur la touche 1 pour saisir un autre numéro de conférence. Sinon, merci de raccrocher, de vérifier le numéro de votre conférence et de rééssayer ultèrieurement." % confid, read=True, max_digits=1) if choice == "1": confid = None continue play_msg_and_hangup(u"Au revoir.") play_msg(u"Vous allez maintenant entrer en conférence. Vous pourrez accéder au menu en appuyant sur la touche étoile.") set_var(asterisk_agi, "GROUP(conference)", confid) set_return(confid) clean_tmp() sys.exit(0) except agi.AGIAppError as e: logging.info('An AGI error stop script : %s' % e) clean_tmp() hangup(asterisk_agi) sys.exit(1) except Exception as e: logging.error(traceback.format_exc()) set_return(None) clean_tmp() hangup(asterisk_agi) sys.exit(1)