def __init__(self): # First time, read from bot and clear them. self.singleton = {k: Memory.Get(k, None) for k in self.KEYS} _LOG.info('User input: %r', self.singleton) for k in self.KEYS: Memory.Set(k, None)
def nearby_transaction(self, max_count, latlng, rules): """Returns transaction data near 'latlng' sorted by 'rules'. Args: max_count: int. latlng: tuple. rules: Rules. None: no criteria entered so far. Returns: list of transaction data. """ db_files = [ '/var/lib/twrealprice/twrealprice.db', '/var/lib/bb8/apps/twrealprice/resource/twrealprice.db', ] start_time = time.time() for db_file in db_files: try: conn = sqlite3.connect(db_file) break except sqlite3.OperationalError: _LOG.warn('Database file is not found: %s', db_file) else: raise IOError('Cannot find database in the following pathes: %s' % ', '.join(db_files)) nearby = [] try: lat_center, lng_center = LatLngIndex(latlng[0], latlng[1]) except ValueError: traceback.print_exc() return [] for lat_in, lng_in in GenNearbyIndex(lat_center, lng_center, 1): for row in conn.execute('select tran_pickle from trans ' + 'where lat_in = %d and lng_in = %d' % (lat_in, lng_in)): nearby += cPickle.loads(str(row[0])) conn.close() end_time = time.time() _LOG.info('DB search (%d data): %.6f sec ...', len(nearby), end_time - start_time) for s in nearby: s['AGE'] = Age(s['建築完成年月'])[:-3] # remove 年 if not rules: rules = twrealprice_rule.Rules() rules.AddPureSortingRules(latlng) rules.CountScore(nearby) rules.Sort() Memory.Set('cached_transaction', rules.Top(MAX_CACHE_TRANS)) return rules.Top(max_count)
def run(unused_content_config, env, unused_variables): user_input = UserInput() msgs = [] # The output messages. # output limit and map size size = (500, 260) if env['platform_type'] == SupportedPlatform.Line: max_count = 3 else: max_count = 5 address = u'' # str. The address user entered. filters_str = u'' # str. The filter used this time. geocoder = GoogleMapsPlaceAPI(api_key=GOOGLE_STATIC_MAP_API_KEY) more_data = user_input.Get('more_data') latlng = Memory.Get('latlng', None) # tuple. User's current location. cached_transaction = Memory.Get('cached_transaction', None) next_data_index = Memory.Get('next_data_index', max_count) if more_data: # Yes, user clicks 'more data', dump data from cache. if not cached_transaction: return [Message(u'請重新輸入地址或條件。')] trans = cached_transaction[next_data_index:next_data_index + max_count] Memory.Set('next_data_index', next_data_index + max_count) if not trans: return [Message(u'沒有更多物件了,請重新輸入地址或條件。')] else: # Nope. User is not entering 'more data'. reset = user_input.Get('reset') location = user_input.Get('location') if reset: latlng = Memory.Get('latlng', None) rules = None # User uploaded current location. elif location and 'coordinates' in location: latlng = (location['coordinates']['lat'], location['coordinates']['long']) rules = Deser(Memory.Get('rules', None)) if rules: rules.AddPureSortingRules(latlng) else: # User entered either an address or criteria; or both. query = user_input.Get('query') rules = twrealprice_rule.Rules.Create() query = rules.ParseQuery(query) address = query.strip() _LOG.info('User query: address=[%s] filters:[%s]', address, rules.filters) if address: if latlng: center = {'lat': latlng[0], 'long': latlng[1]} else: center = None geo_results = geocoder.query_top_n(n=3, address=address, language='zh_TW', region='TW', bounds=[[[20.0, 118.0], [26.0, 123.0]]], center=center) if not geo_results: if rules.filters: msgs.append( Message((u'我不認識這個地址:[%s], 但是我認' u'識條件:[%s]. 請修改一下地址。') % (address, ' '.join(rules.filters)))) address = u'' else: return [Message(u'我不認識這個地址:[%s]' % address)] if len(geo_results) == 1: latlng = (geo_results[0]['location'][0], geo_results[0]['location'][1]) if len(geo_results) > 1: m = Message(buttons_text=u'你指的是以下哪一個地址呢?') for r in geo_results: m.add_button( Message.Button(Message.ButtonType.POSTBACK, r['address'], payload=LocationPayload( r['location'], False))) # If user also entered criteria, save it for later query. if rules.filters: Memory.Set('rules', cPickle.dumps(rules)) # Postpone the AddPureSortingRules until we get latlng. return [m] if not rules.filters: # If user didn't enter filter, use the previous one. rules = Deser(Memory.Get('rules', None)) if rules: _LOG.info('User prev rules: %s', rules.filters) if rules: rules.AddPureSortingRules(latlng) next_data_index = max_count if not latlng: return [Message(u'我還不知道你想查的地方是哪裡。請輸入地址或是送出你的位置。')] Memory.Set('rules', cPickle.dumps(rules)) Memory.Set('latlng', latlng) Memory.Set('next_data_index', next_data_index) twrealprice = TwRealPrice() try: trans = twrealprice.nearby_transaction(max_count, latlng, rules) except IOError: return [Message(u'資料庫找不到,趕快回報給粉絲頁管理員,謝謝。')] if rules and rules.filters: filters_str = u'篩選條件: 「%s」。' % u'」「'.join(rules.filters) if not trans: msg = Message(u'對不起,找不到成交行情喔!試試別的地址或條件。') msgs.append(msg) return msgs # Construct main result cards. main_map = GoogleStaticMapAPIRequestBuilder(GOOGLE_STATIC_MAP_API_KEY, size) style = MarkerStyle() main_map.add_marker(latlng, **style.next()) for s in trans: lat = float(s['latlng'][0]) lng = float(s['latlng'][1]) main_map.add_marker((lat, lng), **style.next()) subtitle = (filters_str + u'搜尋結果僅供參考,詳細完整實價登錄資料,以內政部公佈為準。') msg = Message() b = Message.Bubble(u'%s附近的成交行情' % address, image_url=main_map.build_url(), subtitle=subtitle) msg.add_bubble(b) msgs.append(msg) # At this point, 'trans' must have data to show. i = -1 for s in trans: # In this loop, the strings are concatenated in utf-8 first because the # transaction data come with utf-8 encoded. try: def UnitPrice(s): try: return '%.2f萬' % (float(s['單價每平方公尺']) * twrealprice_rule.M2_PER_PING / 10000) except ValueError: return '--.--' lines = [ ' '.join([ '%d萬' % (int(s['總價元']) / 10000), '(%s * %.2f坪 + %d萬)' % (UnitPrice(s), float(s['建物移轉總面積平方公尺']) / twrealprice_rule.M2_PER_PING, int(s['車位總價元']) / 10000), '%s(%s)' % ( s['建物型態'].split('(')[0], Age(s['建築完成年月']), ), ]).decode('utf-8'), ' '.join([ '%s/共%s' % (s['移轉層次'].replace('層', '樓'), s['總樓層數'].replace('層', '樓')), StreetOnly(s), ]).decode('utf-8'), ' '.join([ RocSlash(s['交易年月日']) + '成交', '%s房%s廳%s衛' % (s['建物現況格局-房'], s['建物現況格局-廳'], s['建物現況格局-衛']), '地坪%.2f' % (float(s['土地移轉總面積平方公尺']) / twrealprice_rule.M2_PER_PING), '車位%.2f坪' % (float(s['車位移轉總面積平方公尺']) / twrealprice_rule.M2_PER_PING), s['車位類別'], s['主要用途'], '備註: ' + s['備註'] if s['備註'] else '', ]).decode('utf-8'), ] if env['platform_type'] == SupportedPlatform.Line: title = lines[0] subtitle = lines[1] + lines[2] else: title = lines[0] + lines[1] subtitle = lines[2] i += 1 msg = Message() msg.add_bubble( Message.Bubble((u'%c: ' % (i + ord('A'))) + title, subtitle=subtitle)) msgs.append(msg) except ValueError: traceback.print_exc() continue return msgs
def run(parser_config, user_input, as_root): """ parser_config schema: { "links": [{ "rule": { "type": "regexp", "params": ["^action1-[0-9]$", "action[23]-1"], "collect_as": { "key": "keyname", "value": "{{matches#1}} # if omit, this will be {{text}} }, "memory_set": { "key": "keyname", "value": "{{matches#1}} # if omit, this will be {{text}} }, }, "end_node_id": 0, "ack_message": "action1 activated" }, { "rule": { "type": "location", "params": null }, "end_node_id": 1, "ack_message": "action2 activated" }] } """ collect = {} for link in parser_config['links']: r_type = link['rule']['type'] def ret(link, variables, collect): end_node_id = link.get('end_node_id', None) if end_node_id: end_node_id = Render(end_node_id, variables) ack_msg = link.get('ack_message', None) return ParseResult(end_node_id, ack_msg, variables, collect) collect_as = link['rule'].get('collect_as') memory_set = link['rule'].get('memory_set') settings_set = link['rule'].get('settings_set') if r_type == 'regexp' and user_input.text: for param in link['rule']['params']: m = re.search(unicode(param), user_input.text) if m: new_vars = { 'text': user_input.text, 'matches': [user_input.text] + list(m.groups()) } if collect_as: collect[collect_as['key']] = Render( collect_as.get('value', '{{text}}'), new_vars) if memory_set: value = memory_set.get('value', '{{text}}') if (isinstance(value, unicode) or isinstance(value, str)): value = Render(value, new_vars) Memory.Set(memory_set['key'], value) if settings_set: value = settings_set.get('value', '{{text}}') if (isinstance(value, unicode) or isinstance(value, str)): value = Render(value, new_vars) Settings.Set(settings_set['key'], value) return ret(link, new_vars, collect) elif r_type == 'location' and user_input.location: if collect_as: collect[collect_as['key']] = user_input.location if memory_set: Memory.Set(memory_set['key'], user_input.location) if settings_set: Settings.Set(settings_set['key'], user_input.location) return ret(link, {'location': user_input.location}, collect) elif r_type == 'event' and user_input.event: for param in link['rule']['params']: if re.search(param, user_input.event.key): new_vars = { 'key': user_input.event.key, 'value': user_input.event.value } if memory_set: value = memory_set.get('value', '{{text}}') if (isinstance(value, unicode) or isinstance(value, str)): value = Render(value, new_vars) Memory.Set(memory_set['key'], value) if settings_set: value = settings_set.get('value', '{{text}}') if (isinstance(value, unicode) or isinstance(value, str)): value = Render(value, new_vars) Settings.Set(settings_set['key'], value) return ret(link, {'event': user_input.event}, {}) elif r_type == 'sticker' and user_input.sticker: for param in link['rule']['params']: if re.search(param, user_input.sticker): return ret(link, {'sticker': user_input.sticker}, {}) if as_root: return ParseResult(errored=True) on_error = parser_config.get('on_error') if on_error: collect_as = on_error.get('collect_as') if collect_as: value = collect_as.get('value', '{{text}}') collect[collect_as['key']] = Render(value, {'text': user_input.text}) variables = {'text': user_input.text} return ParseResult(Render(on_error['end_node_id'], variables), on_error.get('ack_message'), variables, collect, errored=True) return ParseResult(None, 'Invalid input, please re-enter', {'text': user_input.text}, collect, errored=True)
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 run(content_config, unused_env, variables): drama_info = DramaInfo() n_items = content_config.get('n_items', DEFAULT_N_ITEMS) user_id = GetUserId() def append_categories_to_quick_reply(m): m.add_quick_reply( Message.QuickReply(Message.QuickReplyType.TEXT, u'荼蘼')) m.add_quick_reply( Message.QuickReply(Message.QuickReplyType.TEXT, u'The K2 第十集')) m.add_quick_reply( Message.QuickReply(Message.QuickReplyType.TEXT, u'熱門韓劇')) m.add_quick_reply( Message.QuickReply(Message.QuickReplyType.TEXT, u'熱門日劇')) m.add_quick_reply( Message.QuickReply(Message.QuickReplyType.TEXT, u'熱門台劇')) m.add_quick_reply( Message.QuickReply(Message.QuickReplyType.TEXT, u'熱門陸劇')) m.add_quick_reply( Message.QuickReply(Message.QuickReplyType.TEXT, u'通知設定')) return [m] if content_config['mode'] == 'subscribe': event = variables['event'] drama_id = event.value['drama_id'] Memory.Set('last_query_drama_id', drama_id) drama_info.subscribe(user_id, drama_id) episodes = drama_info.get_history(drama_id=drama_id, from_episode=0, backward=True) return ([ Message(u'謝謝您的追蹤,' u'我們會在有更新的時候通知您!'), Message(u'在等待的同時,' u'您可以先看看之前的集數喲!') ] + render_episodes(episodes, variables)) if content_config['mode'] == 'unsubscribe': event = variables['event'] drama_id = event.value['drama_id'] drama_info.unsubscribe(user_id, drama_id) return [Message(u'已成功取消訂閱')] if content_config['mode'] == 'get_history': event = variables['event'] drama_id = event.value['drama_id'] Memory.Set('last_query_drama_id', drama_id) from_episode = event.value['from_episode'] backward = event.value['backward'] episodes = drama_info.get_history(drama_id=drama_id, from_episode=from_episode, backward=backward) return render_episodes(episodes, variables) if content_config['mode'] == 'prompt': m = Message(u'你比較喜歡以下的什麼劇呢?') append_categories_to_quick_reply(m) return [m] country = content_config['mode'].replace('trending_', '') if content_config['mode'] == 'search': query_term = Resolve(content_config['query_term'], variables) dramas = drama_info.search(user_id, query_term, n_items) if dramas: if len(dramas) == 1: Memory.Set('last_query_drama_id', dramas[0].id) m = render_dramas(dramas) append_categories_to_quick_reply(m) return [m] m = Message(u'找不到耶!你可以換個關鍵字或試試熱門的類別:') append_categories_to_quick_reply(m) return [m] if content_config['mode'] == 'search_episode': if len(variables['matches']) == 3: dramas = drama_info.search(user_id, variables['matches'][1], 1) if dramas: Memory.Set('last_query_drama_id', dramas[0].id) drama_id = Memory.Get('last_query_drama_id') if drama_id: try: try: serial_number = int( Resolve(content_config['episode'], variables)) except ValueError: serial_number = convert_to_arabic_numbers( Resolve(content_config['episode'], variables)) episode = drama_info.get_episode(drama_id, serial_number) except Exception: return [Message('沒有這一集喔')] return render_episodes([episode], variables) else: return [Message('請先告訴我你要查的劇名')] m = render_dramas(drama_info.get_trending(user_id, country=country)) append_categories_to_quick_reply(m) return [m]