def setup_prerequisite(self): DatabaseManager.reset() self.bot = Bot(name=u'test', description=u'test', interaction_timeout=120, session_timeout=86400).add() DatabaseManager.flush() config = { 'access_token': 'EAAP0okfsZCVkBAI3BCU5s3u8O0iVFh6NAwFHa7X2bKZCGQ' 'Lw6VYeTpeTsW5WODeDbekU3ZA0JyVCBSmXq8EqwL1GDuZBO' '7aAlcNEHQ3AZBIx0ZBfFLh95TlJWlLrYetzm9owKNR8Qju8' 'HF6qra20ZC6HqNXwGpaP74knlNvQJqUmwZDZD' } self.platform = Platform(name=u'Test platform', bot_id=self.bot.id, type_enum=PlatformTypeEnum.Facebook, provider_ident='1155924351125985', config=config).add() DatabaseManager.flush() self.user_1 = User(platform_id=self.platform.id, platform_user_ident='1153206858057166', last_seen=datetime.datetime(2016, 6, 2, 12, 44, 56, tzinfo=pytz.utc), settings={'subscribe': True}).add() self.user_2 = User(platform_id=self.platform.id, platform_user_ident='1318395614844436', last_seen=datetime.datetime(2016, 6, 2, 12, 44, 56, tzinfo=pytz.utc), settings={'subscribe': True}).add() DatabaseManager.commit()
def test_broadcast_deletion(self): # Test get all broadcasts rv = self.app.get('/api/bots/%d/broadcasts' % self.bot_ids[0]) self.assertEquals(rv.status_code, HTTPStatus.STATUS_OK) data = json.loads(rv.data) broadcast_id = data['broadcasts'][0]['id'] # Set broadcast status to SENT b = Broadcast.get_by(id=broadcast_id, single=True) b.status = BroadcastStatusEnum.SENT DatabaseManager.commit() # Delete the broadcast (should fail becuase it's sent already) rv = self.app.delete('/api/broadcasts/%d' % broadcast_id) self.assertEquals(rv.status_code, HTTPStatus.STATUS_CLIENT_ERROR) data = json.loads(rv.data) # Set broadcast status back to QUEUED b2 = Broadcast.get_by(id=broadcast_id, single=True) b2.status = BroadcastStatusEnum.QUEUED DatabaseManager.commit() # Delete the broadcast rv = self.app.delete('/api/broadcasts/%d' % broadcast_id) self.assertEquals(rv.status_code, HTTPStatus.STATUS_OK) data = json.loads(rv.data) # Make sure we don't have any broadcasts left rv = self.app.get('/api/bots/%d/broadcasts' % self.bot_ids[0]) self.assertEquals(rv.status_code, HTTPStatus.STATUS_OK) data = json.loads(rv.data) self.assertEquals(len(data['broadcasts']), 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)
def process_admin_reply(self, bot, user, unused_user_input=None, unused_input_vars=None): try: if bot.admin_interaction_timeout > 0: user.last_admin_seen = datetime.datetime.now() finally: DatabaseManager.commit()
def setup_prerequisite(self, bot_file): InputTransformation.clear() self.bot = reset_and_setup_bots([bot_file])[0] self.user = User(platform_id=Platform.get_by(id=1, single=True).id, platform_user_ident='blablabla', last_seen=datetime.datetime.now()).add() DatabaseManager.commit()
def delete_broadcast(broadcast_id): """Delete a broadcast.""" broadcast = get_account_broadcast_by_id(broadcast_id) if broadcast.status == BroadcastStatusEnum.SENT: raise AppError(HTTPStatus.STATUS_CLIENT_ERROR, CustomError.ERR_WRONG_PARAM, 'Broadcast not deletable') broadcast.delete() DatabaseManager.commit() return jsonify(message='ok')
def deploy_bot(bot_id): bot = get_account_bot_by_id(bot_id) try: parse_bot(bot.staging, bot.id) except Exception as e: logger.exception(e) raise AppError(HTTPStatus.STATUS_CLIENT_ERROR, CustomError.ERR_WRONG_PARAM, 'Bot definition parsing failed') bot_def = BotDef.add_version(bot.id, bot.staging) bot.staging = None # Clear staging area DatabaseManager.commit() return jsonify(version=bot_def.version)
def create_platform(): """Create a new platform.""" try: platform_json = request.json platform_json['account_id'] = g.account.id platform = parse_platform(platform_json) except Exception as e: logger.exception(e) raise AppError(HTTPStatus.STATUS_CLIENT_ERROR, CustomError.ERR_WRONG_PARAM, 'Platform definition parsing failed') DatabaseManager.commit() return jsonify(platform.to_json(['config']))
def create_broadcast(): """Create a new broadcast.""" try: broadcast_json = request.json broadcast_json['account_id'] = g.account.id broadcast = parse_broadcast(broadcast_json) except Exception as e: logger.exception(e) raise AppError(HTTPStatus.STATUS_CLIENT_ERROR, CustomError.ERR_WRONG_PARAM, 'Broadcast create request failed') DatabaseManager.commit() return jsonify(broadcast.to_json())
def create_bot(): """Create a new bot.""" data = request.json try: jsonschema.validate(data, BOT_CREATE_SCHEMA) except Exception: raise AppError(HTTPStatus.STATUS_CLIENT_ERROR, CustomError.ERR_FORM_VALIDATION, 'schema validation fail') bot = Bot(**data).add() g.account.bots.append(bot) DatabaseManager.commit() return jsonify(bot.to_json(bot.detail_fields))
def add_user(platform, sender): """Add a new user into the system.""" profile_info = get_user_profile(platform, sender) user = User(platform_id=platform.id, platform_user_ident=sender, last_seen=datetime.datetime.now(), **profile_info).add() DatabaseManager.commit() statsd.gauge('users', User.count(), tags=[config.ENV_TAG]) track( TrackingInfo.Event(sender, '%s.User' % platform.type_enum.value, 'Add', profile_info['first_name'])) return user
def test_session_mutable_tracking(self): bot = reset_and_setup_bots(['test/simple.bot'])[0] user = User(platform_id=bot.platforms[0].id, platform_user_ident='', last_seen=datetime.datetime.now(), session=1).add() DatabaseManager.commit() self.assertNotEquals(User.get_by(id=user.id, single=True), None) s = User.get_by(id=user.id, single=True) s.session.message_sent = True DatabaseManager.commit() s = User.get_by(id=user.id, single=True) self.assertEquals(s.session.message_sent, True)
def update_bot(bot_id): """Modify a bot staging area.""" bot = get_account_bot_by_id(bot_id) try: validate_bot_schema(request.json) except Exception as e: logger.exception(e) raise AppError(HTTPStatus.STATUS_CLIENT_ERROR, CustomError.ERR_WRONG_PARAM, 'Bot definition parsing failed') bot.name = request.json['bot']['name'] bot.description = request.json['bot']['description'] bot.staging = request.json DatabaseManager.commit() return jsonify(message='ok')
def update_broadcast(broadcast_id): """Update a broadcast.""" broadcast = get_account_broadcast_by_id(broadcast_id) try: broadcast_json = request.json broadcast_json['account_id'] = g.account.id parse_broadcast(broadcast_json, broadcast.id) except BroadcastUnmodifiableError: raise AppError(HTTPStatus.STATUS_CLIENT_ERROR, CustomError.ERR_WRONG_PARAM, 'Broadcast not modifiable') except Exception as e: logger.exception(e) raise AppError(HTTPStatus.STATUS_CLIENT_ERROR, CustomError.ERR_WRONG_PARAM, 'Broadcast update request failed') DatabaseManager.commit() return jsonify(message='ok')
def test_timestamp_update(self): """Make sure the updated_at timestamp automatically updates on commit.""" account = Account(email='*****@*****.**', passwd='test_hashed').add() DatabaseManager.commit() account.refresh() self.assertEquals(account.created_at, account.updated_at) last_updated = account.updated_at time.sleep(1) account.email = '*****@*****.**' DatabaseManager.commit() account.refresh() self.assertNotEquals(last_updated, account.updated_at)
def setup_prerequisite(self): register_all_modules() self.account1 = Account( name=u'test', email='*****@*****.**').set_passwd('12345678').add() self.account2 = Account( name=u'test2', email='*****@*****.**').set_passwd('12345678').add() DatabaseManager.commit() self.login(self.account1) self.create_bot() self.create_broadcast(self.bot_ids[0]) self.login(self.account2) self.create_bot() self.create_broadcast(self.bot_ids[1]) # Login back as account1 self.login(self.account1)
def setup_prerequisite(self): register_all_modules() self.account1 = Account( name=u'test', email='*****@*****.**').set_passwd('12345678').add() self.account2 = Account( name=u'test2', email='*****@*****.**').set_passwd('12345678').add() DatabaseManager.commit() self.login(self.account1) self.create_bot() self.create_platform('dev/bb8.test.platform') self.login(self.account2) self.create_bot() self.create_platform('dev/bb8.test2.platform') # Login back as account1 self.login(self.account1)
def parse_broadcast(broadcast_json, to_broadcast_id=None): """Parse Broadcast from broadcast definition.""" validate_broadcast_schema(broadcast_json) if callable(to_broadcast_id): to_broadcast_id = to_broadcast_id(broadcast_json) # Validate that the target bot is own by the same account. bot = Bot.get_by(id=broadcast_json['bot_id'], account_id=broadcast_json['account_id'], single=True) if not bot: raise RuntimeError('bot does not exist for broadcast') if to_broadcast_id: broadcast = Broadcast.get_by(id=to_broadcast_id, single=True) if broadcast.status != BroadcastStatusEnum.QUEUED: raise BroadcastUnmodifiableError # Update existing broadcast. logger.info(u'Updating existing Broadcast(id=%d, name=%s) ...', to_broadcast_id, broadcast_json['name']) broadcast = Broadcast.get_by(id=to_broadcast_id, single=True) broadcast.bot_id = broadcast_json['bot_id'] broadcast.name = broadcast_json['name'] broadcast.messages = broadcast_json['messages'] broadcast.scheduled_time = datetime.utcfromtimestamp( broadcast_json['scheduled_time']) broadcast.status = broadcast_json.get('status', broadcast.status) else: # Create a new broadcast. logger.info(u'Creating new Broadcast(name=%s) ...', broadcast_json['name']) broadcast_json['scheduled_time'] = datetime.utcfromtimestamp( broadcast_json['scheduled_time']) broadcast = Broadcast(**broadcast_json).add() DatabaseManager.commit() schedule_broadcast(broadcast) return broadcast
def test_CollectedData_API(self): for i in range(3): CollectedDatum(user_id=self.user_1.id, key='K', value='V%d' % i).add() time.sleep(1) DatabaseManager.commit() g.user = self.user_1 self.assertEquals(CollectedData.GetLast('K'), 'V2') self.assertEquals(CollectedData.Get('K', 3), ['V2', 'V1', 'V0']) self.assertEquals(CollectedData.Get('K', 2, 1), ['V1', 'V0']) # pylint: disable=W0212 for i in range(CollectedData._MAX_RETURN_RESULTS + 10): CollectedDatum(user_id=self.user_2.id, key='K', value='V%d' % i).add() DatabaseManager.commit() g.user = self.user_2 self.assertEquals(len(CollectedData.Get('K', 110)), 100) self.assertEquals(CollectedData.Count('K'), 110)
def setup_prerequisite(self): DatabaseManager.reset() self.bot = Bot(name=u'test', description=u'test', interaction_timeout=120, session_timeout=86400).add() DatabaseManager.commit() config = { 'access_token': 'iHRMgmp3zRLOc6kPCbPNMwEDHyFqLGSy0tyG3uZxnkNlhMKg' '8GVFqMGslcOkmgOAFLlBvvYuXmKF9odhXtsCm3tBxRcPryKr' 'kOvzHBcBvS2zrVGiVmZGh5EBcqazgurYMwVSdgNSrhCm/qp6' '2aR7HAdB04t89/1O/w1cDnyilFU=', 'channel_secret': '335c901df3a1969ca28a48bf6ddcc333' } platform = Platform(bot_id=self.bot.id, name=u'Line', type_enum=PlatformTypeEnum.Line, provider_ident='aitjcize.line', config=config).add() DatabaseManager.commit() self.user = User( platform_id=platform.id, platform_user_ident='U7200f33369e7e586c973c3a9df8feee4', last_seen=datetime.datetime.now()).add() DatabaseManager.commit()
def test_auth(self): DatabaseManager.reset() account = Account(name=u'Test Account 3', email='*****@*****.**').add() some_passwd = 'abcdefg' account.set_passwd(some_passwd) DatabaseManager.commit() account_ = Account.get_by(id=account.id, single=True) self.assertNotEquals(account_.passwd, some_passwd) self.assertEquals(account_.verify_passwd(some_passwd), True) self.assertEquals(account_.verify_passwd('should be wrong'), False) token = account_.auth_token account_t = Account.from_auth_token(token) self.assertEquals(account_.id, account_t.id) fake_token = jwt.encode({ 'iss': 'compose.ai', 'sub': account_.id, 'jti': str(uuid.uuid4()), 'iat': datetime.datetime.utcnow(), 'exp': datetime.datetime.utcnow() + datetime.timedelta(days=14) }, 'im fake secret') with self.assertRaises(RuntimeError): Account.from_auth_token(fake_token) outdated_token = jwt.encode({ 'iss': 'compose.ai', 'sub': account_.id, 'jti': str(uuid.uuid4()), 'iat': datetime.datetime.utcnow() - datetime.timedelta(days=30), 'exp': datetime.datetime.utcnow() - datetime.timedelta(days=15) }, config.JWT_SECRET) with self.assertRaises(RuntimeError): Account.from_auth_token(outdated_token)
def email_register(): data = request.json try: jsonschema.validate(data, REGISTER_SCHEMA) pytz.timezone(data['timezone']) except Exception: raise AppError(HTTPStatus.STATUS_CLIENT_ERROR, CustomError.ERR_FORM_VALIDATION, 'schema validation fail') account = Account.get_by(email=data['email'], single=True) if not account: account = Account(email=data['email']).set_passwd(data['passwd']).add() DatabaseManager.commit() else: raise AppError(HTTPStatus.STATUS_CLIENT_ERROR, CustomError.ERR_USER_EXISTED, 'email %s is already taken' % data['email']) ret = account.to_json() ret[Key.AUTH_TOKEN] = account.auth_token return jsonify(ret)
def test_query_expression_rendering(self): """Test that query expresssion can be query and rendered correctly.""" CollectedDatum(user_id=self.user_1.id, key='data', value='value1').add() DatabaseManager.commit() time.sleep(1) CollectedDatum(user_id=self.user_1.id, key='data', value='value2').add() DatabaseManager.commit() time.sleep(1) CollectedDatum(user_id=self.user_1.id, key='data', value='value3').add() CollectedDatum(user_id=self.user_1.id, key='aaa', value='aaa').add() CollectedDatum(user_id=self.user_2.id, key='data', value='value4').add() DatabaseManager.commit() g.user = self.user_1 m = Message("{{data('data').first|upper}}") self.assertEquals(m.as_dict()['text'], 'VALUE1') m = Message("{{data('data').get(1)}}") self.assertEquals(m.as_dict()['text'], 'value2') m = Message("{{data('data').last}}") self.assertEquals(m.as_dict()['text'], 'value3') m = Message("{{data('data').lru(0)}}") self.assertEquals(m.as_dict()['text'], 'value3') m = Message("{{data('data').lru(1)}}") self.assertEquals(m.as_dict()['text'], 'value2') m = Message("{{data('data').fallback('valuef').get(5)}}") self.assertEquals(m.as_dict()['text'], 'valuef') m = Message("{{data('data').order_by('-created_at').first}}") self.assertEquals(m.as_dict()['text'], 'value3') m = Message("{{data('data').count}}") self.assertEquals(m.as_dict()['text'], '3') # Test error with self.assertRaises(Exception): Message("{{data('data')|some_filter}}") wrong_tmpl = "{{data('some_key').first}}" m = Message(wrong_tmpl) self.assertEquals(m.as_dict()['text'], wrong_tmpl)
def test_Bot_API(self): """Test Bot model APIs.""" DatabaseManager.reset() bots = reset_and_setup_bots(['test/simple.bot', 'test/postback.bot']) bot1 = bots[0] bot2 = bots[1] bot2_node_len = len(bot2.nodes) bot1.delete_all_nodes() DatabaseManager.commit() # All nodes and links related to this bot should be gone. self.assertEquals(bot1.nodes, []) # Make sure delete_all_nodes does not accidentally delete node # of other bot self.assertEquals(len(bot2.nodes), bot2_node_len) # Test bot reconstruction parse_bot_from_file(get_bot_filename('test/simple.bot'), bot1.id) DatabaseManager.commit() self.assertNotEquals(bot1.nodes, []) self.assertEquals(bot1.users, []) User(platform_id=bot1.platforms[0].id, platform_user_ident='blablabla', last_seen=datetime.datetime.now()).add() User(platform_id=bot1.platforms[1].id, platform_user_ident='blablabla2', last_seen=datetime.datetime.now()).add() DatabaseManager.commit() self.assertEquals(len(bot1.users), 2) bot1_id = bot1.id bot1.delete() DatabaseManager.commit() self.assertEquals(Bot.get_by(id=bot1_id, single=True), None)
def setup_prerequisite(self): Account(name=u'test', email='*****@*****.**').set_passwd('12345678').add() DatabaseManager.commit()
def delete_platform(platform_id): """Delete a platform.""" platform = get_account_platform_by_id(platform_id) platform.delete() DatabaseManager.commit() return jsonify(message='ok')
def delete_bot(bot_id): bot = get_account_bot_by_id(bot_id) bot.delete() DatabaseManager.commit() return jsonify(message='ok')
def test_schema(self): """Test database schema and make sure all the tables can be created without problems.""" DatabaseManager.reset() DatabaseManager.commit()
def test_schema_sanity(self): """Populate data into all tables and make sure there are no error.""" DatabaseManager.reset() account = Account(name=u'Test Account', email='*****@*****.**', passwd='test_hashed').add() bot = Bot(name=u'test', description=u'test', interaction_timeout=120, session_timeout=86400).add() account.bots.append(bot) content = ContentModule(id='test', name='Content1', description='desc', module_name='', ui_module_name='').add() parser = ParserModule(id='test', name='Parser1', description='desc', module_name='passthrough', ui_module_name='', variables={}).add() # Test for oauth schema oauth1 = OAuthInfo(provider=OAuthProviderEnum.Facebook, provider_ident='mock-facebook-id').add() oauth2 = OAuthInfo(provider=OAuthProviderEnum.Github, provider_ident='mock-github-id').add() account.oauth_infos.append(oauth1) account.oauth_infos.append(oauth2) DatabaseManager.commit() account_ = Account.get_by(id=account.id, single=True) self.assertNotEquals(account_, None) self.assertEquals(len(account_.oauth_infos), 2) oauth_ = OAuthInfo.get_by(provider_ident='mock-facebook-id', single=True) self.assertNotEquals(oauth_, None) self.assertNotEquals(oauth_.account, None) self.assertEquals(oauth_.account.id, account.id) # Test for bot account.bots.append(bot) DatabaseManager.commit() self.assertNotEquals(Account.get_by(id=account.id, single=True), None) self.assertNotEquals(Bot.get_by(id=bot.id, single=True), None) self.assertNotEquals(ContentModule.get_by(id=content.id, single=True), None) self.assertNotEquals(ParserModule.get_by(id=parser.id, single=True), None) # Check acccount_bot association table self.assertEquals(len(account.bots), 1) self.assertEquals(account.bots[0].id, bot.id) platform = Platform(name=u'Test platform', bot_id=bot.id, type_enum=PlatformTypeEnum.Facebook, provider_ident='facebook_page_id', config={}).add() DatabaseManager.commit() self.assertNotEquals(Platform.get_by(id=platform.id, single=True), None) self.assertEquals(len(bot.platforms), 1) self.assertEquals(bot.platforms[0].id, platform.id) node1 = Node(stable_id='node1', name=u'1', bot_id=bot.id, expect_input=True, content_module_id=content.id, content_config={}, parser_module_id=parser.id, parser_config={}).add() node2 = Node(stable_id='node2', name=u'2', bot_id=bot.id, expect_input=True, content_module_id=content.id, content_config={}, parser_module_id=parser.id, parser_config={}).add() node3 = Node(stable_id='node3', name=u'3', bot_id=bot.id, expect_input=True, content_module_id=content.id, content_config={}, parser_module_id=parser.id, parser_config={}).add() bot.orphan_nodes.append(node3) DatabaseManager.commit() self.assertNotEquals(Node.get_by(id=node1.id, single=True), None) self.assertNotEquals(Node.get_by(id=node2.id, single=True), None) self.assertNotEquals(Node.get_by(id=node3.id, single=True), None) # Test bot_node association table self.assertEquals(bot.orphan_nodes[0].id, node3.id) user = User(platform_id=platform.id, platform_user_ident='', last_seen=datetime.datetime.now()).add() DatabaseManager.commit() self.assertNotEquals(User.get_by(id=user.id, single=True), None) event = Event(bot_id=bot.id, user_id=user.id, event_name='event', event_value={}).add() DatabaseManager.commit() self.assertNotEquals(Event.get_by(id=event.id, single=True), None) collected_datum = CollectedDatum(user_id=user.id, key='key', value={}).add() DatabaseManager.commit() self.assertNotEquals(CollectedDatum.get_by(id=collected_datum.id, single=True), None) self.assertEquals(len(user.colleted_data), 1) self.assertEquals(user.colleted_data[0].id, collected_datum.id) conversation = Conversation(bot_id=bot.id, user_id=user.id, sender_enum=SenderEnum.Bot, msg={}).add() DatabaseManager.commit() self.assertNotEquals(Conversation.get_by(id=conversation.id, single=True), None) # Broadcast bc = Broadcast(account_id=account.id, bot_id=bot.id, name=u'New broadcast', messages=[], scheduled_time=datetime.datetime.utcnow()).add() DatabaseManager.commit() self.assertNotEquals(Broadcast.get_by(id=bc.id, single=True), None) # PublicFeed, Feed account = Account(name=u'Test Account - 1', email='*****@*****.**', passwd='test_hashed').add() feed1 = Feed(url='example.com/rss', type=FeedEnum.RSS, title=u'foo.com', image_url='foo.com/logo').add() feed2 = Feed(url='example.com/rss', type=FeedEnum.RSS, title=u'bar.com', image_url='bar.com/logo').add() feed3 = Feed(url='example.com/rss', type=FeedEnum.RSS, title=u'baz.com', image_url='baz.com/logo').add() account.feeds.append(feed1) account.feeds.append(feed2) account.feeds.append(feed3) DatabaseManager.commit() self.assertNotEquals(Feed.get_by(id=feed1.id, single=True), None) feeds = Feed.search_title('ba') self.assertEquals(len(list(feeds)), 2) pfeed = PublicFeed(url='example.com/rss', type=FeedEnum.RSS, title=u'example.com', image_url='example.com/logo').add() DatabaseManager.commit() self.assertNotEquals(PublicFeed.get_by(id=pfeed.id, single=True), None)
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()