Exemple #1
0
def broadcast_task(broadcast_id):
    with DatabaseSession():
        broadcast = Broadcast.get_by(id=broadcast_id, single=True)
        # The broadcast entry could be delted by user
        if not broadcast:
            return

        # Either broadcast in progress or already sent
        if broadcast.status != BroadcastStatusEnum.QUEUED:
            return

        # The user may have changed the broadcasting time, ignore it as it
        # should be automatically rescheduled.
        if broadcast.scheduled_time >= datetime.now():
            return

        broadcast.status = BroadcastStatusEnum.SENDING
        try:
            DatabaseManager.commit()
        except (InvalidRequestError, IntegrityError):
            # The broadcast task have changed during our modification, retry.
            DatabaseManager.rollback()
            return broadcast_task(broadcast_id)

        # Do the actually broadcast
        bot = Bot.get_by(id=broadcast.bot_id,
                         account_id=broadcast.account_id,
                         single=True)

        # Bot may have been deleted
        if not bot:
            return

        messages = [Message.FromDict(m) for m in broadcast.messages]
        broadcast_message_async(bot, messages)
Exemple #2
0
    def step(self, bot, user, user_input=None, input_vars=None):
        """Main function for executing a node."""

        try:  # pylint: disable=R0101
            now = datetime.datetime.now()

            if user.session is None:
                if user_input:
                    user_input.disable_jump()
                user.goto(Bot.START_STABLE_ID)

            g.user = user
            if user_input:
                if config.STORE_CONVERSATION:
                    Conversation(bot_id=user.bot_id, user_id=user.id,
                                 sender_enum=SenderEnum.Human,
                                 msg=user_input).add()

                # Parse audio as text if there are audio payload
                user_input.ParseAudioAsText(user)
                user_input = user_input.RunInputTransformation()

            # If there was admin interaction, and admin_interaction_timeout
            # haven't reached yet, do not run engine.
            if (bot.admin_interaction_timeout > 0 and
                    ((now - user.last_admin_seen).total_seconds() <
                     bot.admin_interaction_timeout)):
                return

            # Check has been idle for too long, reset it's state if yes.
            if (bot.session_timeout > 0 and
                    ((now - user.last_seen).total_seconds() >
                     bot.session_timeout)):
                user.last_seen = datetime.datetime.now()
                user.goto(Bot.ROOT_STABLE_ID)

            if user_input and user_input.jump():
                node = Node.get_by(stable_id=user_input.jump_node_id,
                                   bot_id=bot.id, single=True)
                # Check if the node belongs to current bot
                if node is None:
                    logger.critical('Invalid jump node_id %s' %
                                    user_input.jump_node_id)
                # If payload button is pressed, we need to jump to the
                # corresponding node if payload's node_id != current_node_id
                elif user_input.jump_node_id != user.session.node_id:
                    user.goto(user_input.jump_node_id)
                    user.session.message_sent = True

            node = Node.get_by(stable_id=user.session.node_id, bot_id=bot.id,
                               eager=['content_module', 'parser_module'],
                               single=True)
            g.node = node

            if node is None:
                logger.critical('Invalid node_id %s' % user.session.node_id)
                user.goto(Bot.ROOT_STABLE_ID)
                user.session.message_sent = True
                return self.step(bot, user, user_input)

            track(TrackingInfo.Pageview(user.platform_user_ident,
                                        '/%s' % node.stable_id))

            # Shared global variables
            global_variables = {
                'user': user.to_json(),
                'bot_id': bot.id
            }

            if not user.session.message_sent:
                env = {
                    'platform_type': SupportedPlatform(
                        user.platform.type_enum.value)
                }
                # Prepare input variables
                input_vars = input_vars or {}
                input_vars.update(global_variables)

                # TODO(aitjcize): figure out how to deal with cm exceptions
                cm = node.content_module.get_module()

                # Send message
                messages = cm.run(node.content_config, env, input_vars)
                messaging.send_message(user, messages)
                user.session.message_sent = True

                # Store InputTransformation in session
                user.session.input_transformation = InputTransformation.get()

                if not node.expect_input:
                    # There are no parser module, which means we are at end of
                    # subgraph.
                    if node.parser_module is None:
                        user.goto(Bot.ROOT_STABLE_ID)
                        user.session.message_sent = True
                        return
                    elif node.parser_module.id == PASSTHROUGH_MODULE_ID:
                        return self.step(bot, user)
                    else:
                        raise RuntimeError('Node `%s\' with parser module '
                                           'not expecting input' % node)
            else:
                # We are already at root node and there is no user input.
                # Display root node again.
                if not user_input and node.stable_id == Bot.ROOT_STABLE_ID:
                    user.session.message_sent = False
                    return self.step(bot, user, user_input)

                # No parser module associate with this node, go back to root
                # node.
                if node.parser_module is None:
                    user.goto(Bot.ROOT_STABLE_ID)
                    user.session.message_sent = True
                    # Run at root instead, so disable jump
                    user_input.disable_jump()
                    return self.step(bot, user, user_input)

                if (not user_input and
                        node.parser_module.id != PASSTHROUGH_MODULE_ID):
                    raise RuntimeError('no user input when running parser')

                result, variables = self.run_parser_module(
                    node, user, user_input, global_variables, False)

                # Node parser failed, try root parser:
                if result.errored and node.stable_id != Bot.ROOT_STABLE_ID:
                    root_result, root_variables = self.run_parser_module(
                        bot.root_node, user, user_input, global_variables,
                        True)

                    # If root paser matched, use root_parser result as result.
                    if not root_result.errored:
                        result = root_result
                        variables = root_variables

                if result.ack_message:
                    self.send_ack_message(user, result.ack_message, variables)

                # end_node_id may be None, either there is a bug or parser
                # module decide not to move at all.
                if result.end_node_id:
                    user.goto(result.end_node_id)

                    # If we are going back the same node, assume there is an
                    # error and we want to retry. don't send message in this
                    # case.
                    if (result.end_node_id == node.stable_id and
                            node.stable_id != Bot.ROOT_STABLE_ID and
                            result.skip_content_module):
                        user.session.message_sent = True
                        return
                else:
                    # There is no link, replay current node.
                    user.session.message_sent = False

                # Run next content module
                return self.step(bot, user, None, variables)
        except Exception as e:
            logger.exception(e)
            # Rollback when error happens, so user won't get stuck in some
            # weird state.
            DatabaseManager.rollback()
        finally:
            user.last_seen = datetime.datetime.now()
            DatabaseManager.commit()