Exemplo n.º 1
0
class AMISpecifier(propertied.Propertied):
    """Manager interface setup/specifier"""
    username = common.StringLocaleProperty(
        "username",
        """Login username for the manager interface""",
    )
    secret = common.StringLocaleProperty(
        "secret",
        """Login secret for the manager interface""",
    )
    password = secret
    server = common.StringLocaleProperty(
        "server",
        """Server IP address to which to connect""",
        defaultValue='127.0.0.1',
    )
    port = common.IntegerProperty(
        "port",
        """Server IP port to which to connect""",
        defaultValue=5038,
    )
    timeout = common.FloatProperty(
        "timeout",
        """Timeout in seconds for an AMI connection timeout""",
        defaultValue=5.0,
    )

    def login(self):
        """Login to the specified manager via the AMI"""
        theManager = manager.AMIFactory(self.username, self.secret)
        return theManager.login(self.server, self.port, timeout=self.timeout)
Exemplo n.º 2
0
class CollectPassword(CollectDigits):
    """Collects some number of password digits from the user"""
    runnerClass = CollectPasswordRunner
    escapeDigits = common.StringLocaleProperty(
        "escapeDigits", """Set of digits which escape from password entry""",
        defaultValue = '',
    )
    soundFile = common.StringLocaleProperty(
        "soundFile", """File (name) for the pre-recorded blurb""",
        defaultValue = 'vm-password',
    )
Exemplo n.º 3
0
Arquivo: menu.py Projeto: daasara/riba
class CollectDigits(Interaction):
    """Collects some number of digits (e.g. an extension) from user"""
    soundFile = common.StringLocaleProperty(
        "soundFile",
        """File (name) for the pre-recorded blurb""",
    )
    textPrompt = common.StringProperty(
        "textPrompt",
        """Textual prompt describing the option""",
    )
    readBack = common.BooleanProperty(
        "readBack",
        """Whether to read the entered value back to the user""",
        defaultValue=False,
    )
    minDigits = common.IntegerProperty(
        "minDigits",
        """Minimum number of digits to collect (only restricted if specified)""",
    )
    maxDigits = common.IntegerProperty(
        "maxDigits",
        """Maximum number of digits to collect (only restricted if specified)""",
    )
    runnerClass = CollectDigitsRunner
    tellInvalid = common.IntegerProperty(
        "tellInvalid",
        """Whether to tell the user that their selection is unrecognised""",
        defaultValue=True,
    )
Exemplo n.º 4
0
Arquivo: menu.py Projeto: daasara/riba
class CollectAudio(Interaction):
    """Collects audio file from the user"""
    prompt = common.ListProperty(
        "prompt",
        """(Set of) prompts to run, can be Prompt instances or filenames
		
		Used by the PromptRunner to produce prompt selections
		""",
    )
    textPrompt = common.StringProperty(
        "textPrompt",
        """Textual prompt describing the option""",
    )
    temporaryFile = common.StringLocaleProperty(
        "temporaryFile",
        """Temporary file into which to record the audio before moving to filename""",
    )
    filename = common.StringLocaleProperty(
        "filename",
        """Final filename into which to record the file...""",
    )
    deleteOnFail = common.BooleanProperty(
        "deleteOnFail",
        """Whether to delete failed attempts to record a file""",
        defaultValue=True)
    escapeDigits = common.StringLocaleProperty(
        "escapeDigits",
        """Set of digits which escape from recording the file""",
        defaultValue='#*0123456789',
    )
    timeout = common.FloatProperty(
        "timeout",
        """Duration to wait for recording (maximum record time)""",
        defaultValue=60,
    )
    silence = common.FloatProperty(
        "silence",
        """Duration to wait for recording (maximum record time)""",
        defaultValue=5,
    )
    beep = common.BooleanProperty(
        "beep",
        """Whether to play a "beep" sound at beginning of recording""",
        defaultValue=True,
    )
    runnerClass = CollectAudioRunner
Exemplo n.º 5
0
class AGISpecifier(propertied.Propertied):
    """Specifier of where we send the user to connect to our AGI"""
    port = common.IntegerProperty(
        "port",
        """IP port on which to listen""",
        defaultValue=4573,
    )
    interface = common.StringLocaleProperty(
        "interface",
        """IP interface on which to listen (local only by default)""",
        defaultValue='127.0.0.1',
    )
    context = common.StringLocaleProperty(
        "context",
        """Asterisk context to which to connect incoming calls""",
        defaultValue='survey',
    )

    def run(self, mainFunction):
        """Start up the AGI server with the given mainFunction"""
        f = fastagi.FastAGIFactory(mainFunction)
        return reactor.listenTCP(self.port, f, 50, self.interface)
Exemplo n.º 6
0
class CollectPasswordRunner(CollectDigitsRunner):
    """Password-runner, checks validity versus expected value"""
    expected = common.StringLocaleProperty(
        "expected", """The value expected/required from the user for this run""",
    )
    def __call__(self, expected, *args, **named):
        """Begin the AGI processing for the menu"""
        self.expected = expected
        return super(CollectPasswordRunner, self).__call__(*args, **named)

    def validEntry(self, digits):
        """Determine whether given digits are considered a "valid" entry"""
        for digit in self.model.escapeDigits:
            if digit in digits:
                raise error.MenuExit(
                    self.model,
                    """User cancelled entry of password""",
                )
        if digits != self.expected:
            return False, "Password doesn't match"
        return True, None
Exemplo n.º 7
0
Arquivo: menu.py Projeto: daasara/riba
class PromptRunner(propertied.Propertied):
    """Prompt formed from list of sub-prompts
	"""
    elements = common.ListProperty(
        "elements",
        """Sub-elements of the prompt to be presented""",
    )
    agi = basic.BasicProperty(
        "agi",
        """The FastAGI instance we're controlling""",
    )
    escapeDigits = common.StringLocaleProperty(
        "escapeDigits",
        """Set of digits which escape from playing the prompt""",
    )
    timeout = common.FloatProperty(
        "timeout",
        """Timeout on data-entry after completed reading""",
    )

    def __call__(self):
        """Return a deferred that chains all of the sub-prompts in order
		
		Returns from the first of the sub-prompts that recevies a selection
		
		returns str(digit) for the key the user pressed
		"""
        return self.onNext(None)

    def onNext(self, result, index=0):
        """Process the next operation"""
        if result is not None:
            return result
        try:
            element = self.elements[index]
        except IndexError, err:
            # okay, do a waitForDigit from timeout seconds...
            return self.agi.waitForDigit(self.timeout).addCallback(
                self.processKey).addCallback(self.processLast)
        else:
Exemplo n.º 8
0
class Option(propertied.Propertied):
    """A single menu option that can be chosen by the user"""
    option = common.StringLocaleProperty(
        "option", """Keypad values which select this option (list of characters)""",
    )
Exemplo n.º 9
0
class MenuRunner(Runner):
    """User's single interaction with a given menu"""
    def defaultEscapeDigits(prop, client):
        """Return the default escape digits for the given client"""
        if client.model.tellInvalid:
            escapeDigits = client.model.ALL_DIGITS
        else:
            escapeDigits = "".join([o.option for o in client.model.options])
        return escapeDigits
    escapeDigits = common.StringLocaleProperty(
        "escapeDigits", """Set of digits which escape from prompts to choose option""",
        defaultFunction = defaultEscapeDigits,
    )
    del defaultEscapeDigits # clean up namespace

    def __call__(self, *args, **named):
        """Begin the AGI processing for the menu"""
        self.readMenu()
        return self.finalDF

    def readMenu(self, result=None):
        """Read our menu to the user"""
        runner = self.promptAsRunner(self.model.prompt)
        return runner().addCallback(self.onReadMenu).addErrback(self.returnError)

    def onReadMenu(self, pressed):
        """Deal with succesful result from reading menu"""
        log.info("""onReadMenu: %r""", pressed)
        if not pressed:
            self.alreadyRepeated += 1
            if self.alreadyRepeated >= self.model.maxRepetitions:
                log.warn("""User did not complete menu selection for %s, timing out""", self.model)
                if not self.finalDF.called:
                    raise error.MenuTimeout(
                        self.model,
                        """User did not finish selection in %s passes of menu""" % (
                            self.alreadyRepeated,
                        )
                    )
                return None
            return self.readMenu()
        else:
            # Yay, we got an escape-key pressed
            for option in self.model.options:
                if pressed in option.option:
                    if callable(option):
                        # allow for chaining down into sub-menus and the like...
                        # we return the result of calling the option via self.finalDF
                        return defer.maybeDeferred(option, pressed, self).addCallbacks(
                            self.returnResult, self.returnError
                        )
                    elif hasattr(option, 'onSuccess'):
                        return defer.maybeDeferred(option.onSuccess, pressed, self).addCallbacks(
                            self.returnResult, self.returnError
                        )
                    else:
                        return self.returnResult([(option,pressed),])
            # but it wasn't anything we expected...
            if not self.model.tellInvalid:
                raise error.MenuUnexpectedOption(
                    self.model, """User somehow selected %r, which isn't a recognised option?""" % (pressed,),
                )
            else:
                return self.agi.getOption(
                    self.model.INVALID_OPTION_FILE, self.escapeDigits,
                    timeout=0,
                ).addCallback(self.readMenu).addErrback(self.returnError)
Exemplo n.º 10
0
class CollectAudioRunner(Runner):
    """Audio-collection runner, records user audio to a file on the asterisk server"""
    escapeDigits = common.StringLocaleProperty(
        "escapeDigits", """Set of digits which escape from recording""",
        defaultFunction = lambda prop, client: client.model.escapeDigits,
        setDefaultOnGet = False,
    )
    def __call__(self, *args, **named):
        """Begin the AGI processing for the menu"""
        self.readPrompt()
        return self.finalDF

    def readPrompt(self, result=None):
        """Begin process of reading audio from the user"""
        if self.model.prompt:
            # wants us to read a prompt to the user before recording...
            runner = self.promptAsRunner(self.model.prompt)
            runner.timeout = 0.1
            return runner().addCallback(self.onReadPrompt).addErrback(self.returnError)
        else:
            return self.collectAudio().addErrback(self.returnError)

    def onReadPrompt(self, result):
        """We've finished reading the prompt to the user, check for escape"""
        log.info('Finished reading prompt for collect audio: %r', result)
        if result and result in self.escapeDigits:
            raise error.MenuExit(
                self.model,
                """User cancelled entry of audio during prompt""",
            )
        else:
            return self.collectAudio()

    def collectAudio( self ):
        """We're supposed to record audio from the user with our model's parameters"""
        # XXX use a temporary file for recording the audio, then move to final destination
        log.debug('collectAudio')
        if hasattr(self.model, 'temporaryFile'):
            filename = self.model.temporaryFile
        else:
            filename = self.model.filename
        df = self.agi.recordFile(
            filename=filename,
            format=self.model.format,
            escapeDigits=self.escapeDigits,
            timeout=self.model.timeout,
            offsetSamples=None,
            beep=self.model.beep,
            silence=self.model.silence,
        ).addCallbacks(
            self.onAudioCollected, self.onAudioCollectFail,
        )
        if hasattr(self.model, 'temporaryFile'):
            df.addCallback(self.moveToFinal)
        return df

    def onAudioCollected(self, result):
        """Process the results of collecting the audio"""
        digits, typeOfExit, endpos = result
        if typeOfExit in ('hangup', 'timeout'):
            # expected common-case for recording...
            return self.returnResult((self,(digits,typeOfExit,endpos)))
        elif typeOfExit =='dtmf':
            raise error.MenuExit(
                self.model,
                """User cancelled entry of audio""",
            )
        else:
            raise ValueError("""Unrecognised recordFile results: (%s, %s %s)""" % (
                digits, typeOfExit, endpos,
            ))

    def onAudioCollectFail(self, reason):
        """Process failure to record audio"""
        log.error(
            """Failure collecting audio for CollectAudio instance %s: %s""",
            self.model, reason.getTraceback(),
        )
        return reason # re-raise the error...

    def moveToFinal(self, result):
        """On succesful recording, move temporaryFile to final file"""
        log.info(
            'Moving recorded audio %r to final destination %r',
            self.model.temporaryFile, self.model.filename
        )
        import os
        try:
            os.rename(
                '%s.%s' % (self.model.temporaryFile, self.model.format),
                '%s.%s' % (self.model.filename, self.model.format),
            )
        except (OSError, IOError), err:
            log.error(
                """Unable to move temporary recording file %r to target file %r: %s""",
                self.model.temporaryFile, self.model.filename,
                # XXX would like to use getException here...
                err,
            )
            raise
        return result
Exemplo n.º 11
0
class Argument(propertied.Propertied):
    """Representation of a single argument on a callable object"""
    name = common.StringLocaleProperty(
        'name',
        """The argument's name, as a simple string""",
    )
    default = basic.BasicProperty(
        'default',
        """Default-value for the argument, may be NULL/unavailable""",
    )
    baseType = basic.BasicProperty(
        'baseType',
        """Base data-type for the argument, may be NULL/unavailable""",
    )

    def __init__(self, name, default=__NULL__, baseType=__NULL__, **named):
        """Initialize the Callable object

		name -- the argument name
		default -- if provided, will provide the default value
			for the argument
		baseType -- if provided, will allow for type checking
			and coercion of arguments before calling the callable
			object.
		"""
        if default is not __NULL__:
            named["default"] = default
        if baseType is not __NULL__:
            named["baseType"] = baseType
        super(Argument, self).__init__(name=name, **named)

    def __str__(self, ):
        """Create a friendly string representation"""
        fragments = [repr(self.name)]
        if hasattr(self, "default"):
            fragments.append(repr(self.default))
        if hasattr(self, "baseType"):
            fragments.append(repr(self.baseType))
        return """%s(%s)""" % (
            self.__class__.__name__,
            ", ".join(fragments),
        )

    __repr__ = __str__

    def __eq__(self, other):
        """Determine whether other is our equivalent

		returns true if other is of the same class, with
		the same primary attributes
		"""
        if self.__class__ is not other.__class__:
            return 0
        NULL = []
        for nm in ['name', 'default', 'baseType']:
            if hasattr(self, nm) and not hasattr(other, nm):
                return 0
            elif not hasattr(self, nm) and hasattr(other, nm):
                return 0
            elif hasattr(self, nm):
                if getattr(self, nm) != getattr(other, nm):
                    return 0
        return 1

    ### Data-type API
    def check(cls, value):
        """Strict check to see if value is an instance of cls"""
        return isinstance(value, cls)

    check = classmethod(check)

    def coerce(cls, value):
        """Coerce value to a cls instance

		Accepted forms:
			("name",)
			("name",default)
			("name",default,baseType)
			"name"
			{ ** } # passed to the initialiser
		"""
        if cls.check(value):
            return value
        if isinstance(value, (tuple, list)) and value and len(value) < 4:
            items = {}
            for item, name in zip(value, ['name', 'default',
                                          'baseType'][:len(value)]):
                items[name] = item
            return cls(**items)
        elif isinstance(value, str):
            return cls(name=value)
        elif isinstance(value, dict):
            return cls(**value)
        raise TypeError("""Don't know how to convert %r to a %s object""" %
                        (value, cls.__name__))

    coerce = classmethod(coerce)
Exemplo n.º 12
0
class PromptRunner(propertied.Propertied):
    """Prompt formed from list of sub-prompts
    """
    elements = common.ListProperty(
        "elements",
        """Sub-elements of the prompt to be presented""",
    )
    agi = basic.BasicProperty(
        "agi",
        """The FastAGI instance we're controlling""",
    )
    escapeDigits = common.StringLocaleProperty(
        "escapeDigits",
        """Set of digits which escape from playing the prompt""",
    )
    timeout = common.FloatProperty(
        "timeout",
        """Timeout on data-entry after completed reading""",
    )

    def __call__(self):
        """Return a deferred that chains all of the sub-prompts in order

        Returns from the first of the sub-prompts that recevies a selection

        returns str(digit) for the key the user pressed
        """
        return self.onNext(None)

    def onNext(self, result, index=0):
        """Process the next operation"""
        if result is not None:
            return result
        try:
            element = self.elements[index]
        except IndexError as err:
            # okay, do a waitForDigit from timeout seconds...
            return self.agi.waitForDigit(self.timeout).addCallback(
                self.processKey).addCallback(self.processLast)
        else:
            df = element.read(self.agi, self.escapeDigits)
            df.addCallback(self.processKey)
            df.addCallback(self.onNext, index=index + 1)
            return df

    def processKey(self, result):
        """Does the pressed key belong to escapeDigits?"""
        if isinstance(result, tuple):
            # getOption result...
            if result[1] == 0:
                # failure during load of the file...
                log.warn("Apparent failure during load of audio file: %s",
                         self.value)
                result = 0
            else:
                result = result[0]
            if isinstance(result, str):
                if result:
                    result = ord(result)
                else:
                    result = 0
        if result:  # None or 0
            # User pressed a key during the reading...
            key = chr(result)
            if key in self.escapeDigits:
                log.info('Exiting early due to user press of: %r', key)
                return key
            else:
                # we don't warn user in this menu if they press an unrecognised key!
                log.info(
                    'Ignoring user keypress because not in escapeDigits: %r',
                    key)
            # completed reading without any escape digits, continue reading
        return None

    def processLast(self, result):
        if result is None:
            result = ''
        return result