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 get_account_bot_by_id(bot_id): bot = Bot.get_by(id=bot_id, account_id=g.account.id, single=True) if bot is None: raise AppError(HTTPStatus.STATUS_CLIENT_ERROR, CustomError.ERR_NOT_FOUND, 'bot_id: %d not found' % bot_id) return bot
def parse_platform(platform_json, to_platform_id=None, source='platform_json'): """Parse Platform from platform definition. If *to_platform_id* is specified, update existing platform specified by *to_platform_id* instead of creating a new platform. If *to_platform_id* is a callable. The result of the call is used as the platform_id. """ validate_platform_schema(platform_json) # Validate plaform-specific schema provider = get_messaging_provider( PlatformTypeEnum(platform_json['type_enum'])) try: jsonschema.validate(platform_json['config'], provider.get_config_schema()) except jsonschema.exceptions.ValidationError: logger.error('Platform config validate failed for `%s\'!', source) raise if callable(to_platform_id): to_platform_id = to_platform_id(platform_json) bot_id = platform_json.get('bot_id') if bot_id: bot = Bot.get_by(id=bot_id, account_id=platform_json['account_id'], single=True) if not bot: raise RuntimeError('bot does not exist') if to_platform_id: # Update existing platform. logger.info(u'Updating existing Platform(id=%d, name=%s) from %s ...', to_platform_id, platform_json['name'], source) platform = Platform.get_by(id=to_platform_id, single=True) platform.bot_id = bot_id platform.name = platform_json['name'] platform.deployed = platform_json['deployed'] platform.type_enum = platform_json['type_enum'] platform.provider_ident = platform_json['provider_ident'] platform.config = platform_json['config'] else: # Create a new platform. logger.info(u'Creating new Platform(name=%s) from %s ...', platform_json['name'], source) platform = Platform(**platform_json).add() DatabaseManager.flush() return platform
def Broadcast(self, request, unused_context): with DatabaseSession(): bot = Bot.get_by(id=request.bot_id, single=True) if not bot: raise RuntimeError('Bot<%d> does not exist' % request.bot_id) eta = None if request.eta == 0 else request.eta messages_dict = cPickle.loads(request.messages_object) if request.static: msgs = [Message.FromDict(m, {}) for m in messages_dict] messaging_tasks.broadcast_message_async(bot, msgs, eta) else: users = User.get_by(bot_id=request.bot_id) messaging_tasks.push_message_from_dict_async( users, messages_dict, eta) return app_service_pb2.Empty()
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_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 parse_bot(bot_json, to_bot_id=None, source='bot_json'): """Parse Bot from bot definition. If *to_bot_id* is specified, update existing bot specified by *to_bot_id* instead of creating a new bot. If *to_bot_id* is a callable. The result of the call is used as the bot_id. """ validate_bot_schema(bot_json, source=source) bot_desc = bot_json['bot'] if callable(to_bot_id): to_bot_id = to_bot_id(bot_desc) if to_bot_id: # Update existing bot. logger.info(u'Updating existing Bot(id=%d, name=%s) from %s ...', to_bot_id, bot_desc['name'], source) bot = Bot.get_by(id=to_bot_id, single=True) bot.delete_all_nodes() bot.name = bot_desc['name'] bot.description = bot_desc['description'] bot.interaction_timeout = bot_desc['interaction_timeout'] bot.admin_interaction_timeout = bot_desc['admin_interaction_timeout'] bot.session_timeout = bot_desc['session_timeout'] bot.ga_id = bot_desc.get('ga_id', None) bot.settings = bot_desc['settings'] DatabaseManager.flush() else: # Create a new bot logger.info(u'Creating new Bot(name=%s) from %s ...', bot_desc['name'], source) bot = Bot( name=bot_desc['name'], description=bot_desc['description'], interaction_timeout=bot_desc['interaction_timeout'], admin_interaction_timeout=bot_desc['admin_interaction_timeout'], session_timeout=bot_desc['session_timeout'], ga_id=bot_desc.get('ga_id', None), settings=bot_desc['settings']).add() DatabaseManager.flush() # Bind Platform with Bot platforms = bot_json.get('platforms', {}) for unused_name, provider_ident in platforms.iteritems(): platform = Platform.get_by(provider_ident=provider_ident, single=True) if platform is None: raise RuntimeError('associated platform `%s\' does not exist', provider_ident) # Bind platform.bot_id = bot.id provider = get_messaging_provider(platform.type_enum) if not platform.deployed or (config.DEPLOY and platform.deployed): provider.apply_settings(platform.config, bot.settings) DatabaseManager.flush() nodes = bot_desc['nodes'] id_map = {} # Mapping of stable_id to id # Build node for stable_id, node in nodes.iteritems(): try: cm = ContentModule.get_by(id=node['content_module']['id'], single=True) if cm is None: raise RuntimeError('Content_module `%d\' does not exist', node['content_module']['id']) jsonschema.validate(node['content_module']['config'], cm.get_module().schema()) except jsonschema.exceptions.ValidationError: logger.error( 'Node `%s\' content module config validation ' 'failed', stable_id) raise n = Node(stable_id=stable_id, bot_id=bot.id, name=unicode(node['name']), description=unicode(node['description']), expect_input=node['expect_input'], content_module_id=node['content_module']['id'], content_config=node['content_module']['config']).add() if 'parser_module' in node: n.parser_module_id = node['parser_module']['id'] DatabaseManager.flush() id_map[stable_id] = n.id # Validate that parser module linkages are present in this bot file. for stable_id, node in nodes.iteritems(): n = Node.get_by(id=id_map[stable_id], single=True) if n.parser_module is not None: nodes = bot_json['bot']['nodes'] n.parser_config = node['parser_module']['config'] pm = n.parser_module.get_module() try: jsonschema.validate(n.parser_config, pm.schema()) except jsonschema.exceptions.ValidationError: logger.error( 'Node `%s\' parser module config validation ' 'failed', stable_id) raise for end_node_id in pm.get_linkages(n.parser_config): if end_node_id is not None: if re.search(HAS_VARIABLE_RE, end_node_id): logger.info('Rendered end_node_id `%s\', check ' 'skipped ...' % end_node_id) continue if end_node_id not in id_map.keys(): raise RuntimeError('end_node_id `%s\' is invalid' % end_node_id) DatabaseManager.flush() return bot
def find_bot_by_name(bot_desc): bot = Bot.get_by(name=bot_desc['name'], single=True) if bot: return bot.id else: return None
def run(parser_config, user_input, unused_as_root): """ { "links": [ { "action_ident": "continue", "end_node_id": null, "ack_message": "" }, { "action_ident": "done", "end_node_id": "[[root]]", "ack_message": "" } ] } """ if user_input.text: if u'重設' in user_input.text or 'reset' in user_input.text.lower(): Memory.Clear() return ParseResult(ack_message=u'讓我們重新開始吧!') if user_input.event: event = user_input.event if event.key == 'CONTROL_FLOW': if event.value == 'reset': Memory.Clear() return ParseResult(parser_config['done'], ack_message=u'放棄操作') elif event.key == 'SELECT_BOT': Memory.Set('bot', event.value) elif event.key == 'SELECT_OP': Memory.Set('operation', event.value) elif event.key == 'MESSAGE_INPUT': if event.value == 'done': Memory.Set('status', 'preview_message') elif event.value == 'restart': Memory.Set('broadcast_message', None) Memory.Set('status', None) elif event.key == 'CONFIRM_MESSAGE': if event.value: bot = Bot.get_by(id=Memory.Get('bot'), single=True) if not bot: return ParseResult(ack_message=u'發生錯誤,找不到對應的' u'機器人') msgs = [ Message.FromDict(raw_msg) for raw_msg in Memory.Get('broadcast_message') ] BroadcastMessage(bot, msgs) Memory.Clear() return ParseResult(parser_config['done'], ack_message=u'您的訊息已送出!') else: Memory.Set('broadcast_message', None) status = Memory.Get('status') if status == 'input_broadcast_message': broadcast_message = Memory.Get('broadcast_message') or [] broadcast_message.append(user_input.raw_message) Memory.Set('broadcast_message', broadcast_message) return ParseResult(skip_content_module=False)
def run(content_config, unused_env, variables): """ content_config schema: { "bot_admins": { "platform_user_ident_1": ["bot_id_1", "bot_id_2" ..], ... } } """ user = g.user bots = content_config['bot_admins'].get(user.platform_user_ident) if not bots: return [] bot = Memory.Get('bot') if not bot: msgs = [ Message(u'嗨 {{user.first_name}},你想要操作哪隻機器人呢?', variables=variables) ] page = 1 m = Message() msgs.append(m) bubble = Message.Bubble('第 %d 頁' % page) for bot_id in bots: bot = Bot.get_by(id=bot_id, single=True) if not bot: continue bubble.add_button( Message.Button(Message.ButtonType.POSTBACK, title=bot.name, payload=EventPayload('SELECT_BOT', bot_id))) if len(bubble.buttons) == 3: m.add_bubble(bubble) page += 1 bubble = Message.Bubble(u'第 %d 頁' % page) if len(bubble.buttons): m.add_bubble(bubble) msgs[-1].add_quick_reply( Message.QuickReply(Message.QuickReplyType.TEXT, u'放棄', payload=EventPayload('CONTROL_FLOW', 'reset'), acceptable_inputs=['(?i)giveup', '(?i)reset'])) return msgs operation = Memory.Get('operation') if not operation: m = Message(buttons_text=u'你想要執行什麼動作呢?') m.add_button( Message.Button(Message.ButtonType.POSTBACK, title=u'廣播訊息', payload=EventPayload('SELECT_OP', 'broadcast'))) m.add_button( Message.Button(Message.ButtonType.POSTBACK, title=u'放棄', payload=EventPayload('CONTROL_FLOW', 'reset'))) return [m] if operation == 'broadcast': broadcast_message = Memory.Get('broadcast_message') status = Memory.Get('status') if status == 'input_broadcast_message': m = Message(u'你可以繼續輸入下一則訊息:') m.add_quick_reply( Message.QuickReply( Message.QuickReplyType.TEXT, u'完成', payload=EventPayload('MESSAGE_INPUT', 'done'), acceptable_inputs=[u'好了', u'(?i)done', '(?i)y'])) m.add_quick_reply( Message.QuickReply( Message.QuickReplyType.TEXT, u'重來', payload=EventPayload('MESSAGE_INPUT', 'restart'), acceptable_inputs=[u'好了', u'(?i)restart', '(?i)cancel'])) m.add_quick_reply( Message.QuickReply( Message.QuickReplyType.TEXT, u'放棄', payload=EventPayload('CONTROL_FLOW', 'reset'), acceptable_inputs=['(?i)giveup', '(?i)reset'])) return [m] elif not broadcast_message: Memory.Set('status', 'input_broadcast_message') return [Message(u'請輸入你要廣播的訊息:')] elif status == 'preview_message': msgs = [Message(u'請確認你要廣播的訊息:')] for raw_msg in broadcast_message: msgs.append(Message.FromDict(raw_msg)) m = msgs[-1] m.add_quick_reply( Message.QuickReply(Message.QuickReplyType.TEXT, u'確認', payload=EventPayload( 'CONFIRM_MESSAGE', True), acceptable_inputs=[u'是', '(?i)y', '(?i)ok'])) m.add_quick_reply( Message.QuickReply( Message.QuickReplyType.TEXT, u'取消', payload=EventPayload('CONFIRM_MESSAGE', False), acceptable_inputs=[u'是', '(?i)no', '(?i)cancel'])) return msgs return [Message(u'錯誤的操作')]
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)