def to_message_element(response): if isinstance(response, QuickReply): message = {"text": response.title} payload = json.dumps( response.payload if response.payload else response.title, default=json_serialize) key = 'callback_' + hashlib.md5(payload).hexdigest() db = get_redis() db.set(key, payload, ex=3600 * 24 * 7) message['callback_data'] = json.dumps(key) return [message] elif isinstance(response, Button): message = {"text": response.title} if response.payload: payload = json.dumps(response.payload, default=json_serialize) key = 'callback_' + hashlib.md5(payload).hexdigest() db = get_redis() db.set(key, payload, ex=3600 * 24 * 7) message['callback_data'] = json.dumps(key) return [message] elif response.url: message['url'] = response.url return [message] return [] return None
def accept_request(body, num_tries=1) -> bool: if body['type'] == 'message': uid = body['from']['id'] chat_id = body['conversation']['id'] get_redis().set('chat_message_id:{}'.format(chat_id), body['id']) # TODO MicrosoftInterface.set_base_url(chat_id, body['serviceUrl']) MicrosoftInterface.set_bot_id(chat_id, body['recipient']['id']) accept_user_message.delay(MicrosoftInterface.name, uid, body, chat_id=chat_id) return True return False
def load_profile(uid, cache=True): fbid = FacebookInterface.uid_to_fbid(uid) db = get_redis() key = 'fb_profile_' + fbid if not cache or not db.exists(key): print('Loading fb profile...') url = "https://graph.facebook.com/v2.6/" + fbid params = { 'fields': 'first_name,last_name,profile_pic,locale,timezone,gender', 'access_token': FacebookInterface.get_page_token_for_uid(uid) } res = requests.get(url, params=params) if not res.status_code == requests.codes.ok: print("!!!!!!!!!!!!!!!!!!!!!!! ERROR load_profile " + res.status_code) return None db.set(key, json.dumps(res.json()), ex=3600 * 24 * 14) # save value, expire in 14 days return json.loads(db.get(key).decode('utf-8'))
def record_bot_message(message): record = {'type': type(message).__name__} if isinstance(message, TextMessage): record['text'] = message.text print('Recording bot message: {}'.format(record)) db = get_redis() db.lpush('test_actions', json.dumps({'type': 'bot', 'body': record}))
def parse_message(raw_message, num_tries=1): print(raw_message) db = get_redis() if 'callback_query' in raw_message: key = json.loads(raw_message['callback_query']['data'], object_hook=json_deserialize) if not db.exists(key): return {'entities': None, 'type': 'postback'} payload = json.loads(db.get(key).decode(encoding='UTF-8')) print("PAYLOAD", payload) payload['_message_text'] = [{'value': None}] return {'entities': payload, 'type': 'postback'} elif 'message' in raw_message: if 'sticker_id' in raw_message['message']: return TelegramInterface.parse_sticker( raw_message['message']['sticker_id']) if 'attachments' in raw_message['message']: attachments = raw_message['message']['attachments'] return TelegramInterface.parse_attachments(attachments) if 'quick_reply' in raw_message['message']: payload = json.loads( raw_message['message']['quick_reply'].get('payload'), object_hook=json_deserialize) if payload: payload['_message_text'] = [{ 'value': raw_message['message']['text'] }] return {'entities': payload, 'type': 'postback'} if 'text' in raw_message['message']: return parse_text_message(raw_message['message']['text']) return {'type': 'undefined'}
def accept_schedule_all_users(callback_name): print('Accepting scheduled callback {}'.format(callback_name)) db = get_redis() interface_names = db.hgetall('session_interface') for uid in interface_names: interface_name = interface_names[uid].decode('utf-8') accept_schedule_callback(interface_name, uid.decode('utf-8'), callback_name)
def record_start(): logging.warning('Starting recording') db = get_redis() db.delete('test_actions') message = TextMessage(text="Starting to record ;)")\ .add_button(PayloadButton(title="Stop recording", payload={"test_record": "stop"})) return message
def _load_from_cache(self, text): if self.cache: db = get_redis() if db.hexists('wit_cache', text): parsed = pickle.loads(db.hget('wit_cache', text)) self.log.debug('Got cached wit key: "{}" = {}'.format(self.cache_key, parsed)) return parsed return None
def get_bot_id(chat_id): if chat_id is None: raise Exception('Chat id must not be null') redis = get_redis() bot_id = redis.get('ms_reply_botid_' + str(chat_id)) if bot_id is None: raise Exception('Bot ID not found for chat {}'.format( str(chat_id))) return bot_id.decode()
def accept_schedule_all_users(callback_name): print('Accepting scheduled callback {}'.format(callback_name)) db = get_redis() interface_names = db.hgetall('session_interface') for uid in interface_names: interface_name = interface_names[uid].decode('utf-8') # TODO revise this interface = create_from_name(interface_name) session = ChatSession(interface, uid.decode('utf-8')) accept_schedule_callback(session.to_json(), callback_name)
def _run_test_module(name, benchmark=False): import importlib module = importlib.import_module('tests.' + name) importlib.reload(module) result = _run_test_actions(name, module.actions, benchmark=benchmark) test = {'name': name, 'result': result} db = get_redis() db.hset('test_results', name, json.dumps(test)) return test
def record_start(): print('Starting recording') db = get_redis() db.delete('test_actions') return TextMessage("Starting recording ;)", buttons=[{ 'title': 'Stop recording', 'payload': { 'test_record': 'stop' } }])
def accept_request(body): uid = body['user']['userId'] chat_id = body['conversation']['conversationId'] meta = {"uid": uid, "chat_id": chat_id} profile = Profile(uid, None, None) session = ChatSession(GoogleActionsInterface, chat_id, meta, profile) accept_user_message.delay(session.to_json(), body).get() # responses = GoogleActionsInterface.response_cache.get(session.chat_id) responses = get_redis().get( "response_for_{id}".format(id=session.chat_id)) # TODO return GoogleActionsInterface.convert_responses( session, responses.decode('utf8'))
def set_bot_id(chat_id, bot_id): """ Because the microsoft bot api doesn't send the requests to recipient "BOT_ID", but to recipient "BOT_ID@nnnn" instead and this ID is required to reply, it is saved in redis. When MS updates their documentation about IDs this will hopefully be removed. :param chat_id: :param bot_id: :return: """ if chat_id is None or bot_id is None: raise Exception('Chat id and bot id must not be null') redis = get_redis() redis.set('ms_reply_botid_' + str(chat_id), str(bot_id))
def run_all_tests(request): modules = _get_test_modules('./tests/') print('Running tests: {}'.format(modules)) tests = [] db = get_redis() db.delete('test_results') for module in modules: print('Running tests "{}"'.format(module)) tests.append(_run_test_module(module)) db.set('test_time', str(datetime.datetime.now())) return JsonResponse(data=tests, safe=False)
def record_user_message(message_type, message): print('Recording user {}: {}'.format(message_type, message)) db = get_redis() db.lpush( 'test_actions', json.dumps( { 'type': 'user', 'body': { 'type': message_type, 'entities': message } }, default=json_serialize))
def persist_callback(payload) -> str: """ Persists payload to be used with Telegram callbacks (which can be max. 64B) and returns a string that can be used to retrieve the payload from redis. The payload will be persisted for a week. :param payload Payload to be persisted in Redis. :return Unique string associated with the payload, which can be sent to Telegram. """ redis = get_redis() key = ''.join(random.choice(string.hexdigits) for i in range(63)) while redis.exists(key): key = ''.join(random.choice(string.hexdigits) for i in range(63)) redis.set(key, payload, ex=3600 * 24 * 7) return key
def get_result(): db = get_redis() actions = db.lrange('test_actions', 0, -1) response = """from golem.core.responses import * from golem.core.tests import * actions = [] """ state = None for action in actions[::-1]: action = json.loads(action.decode('utf-8'), object_hook=json_deserialize) body = action['body'] print('Action body: {}'.format(body)) if action['type'] == 'user': if body['type'] == 'message': text_entity = body['entities']['_message_text'][0] text = text_entity['value'] message = 'UserTextMessage("{}")'.format(text) for entity in body['entities']: if entity.startswith('_'): continue value = body['entities'][entity][0].get('value') if isinstance(value, str): message += '.produces_entity("{}","{}")'.format( entity, value) else: message += '.produces_entity("{}")'.format(entity) if body['type'] == 'postback': text_entity = body['entities'].get('_message_text')[0] text = text_entity['value'] message = 'UserButtonMessage("{}")'.format(text) response += "\n\n" + 'actions.append({})'.format( message) + "\n" elif action['type'] == 'bot': message = 'BotMessage({})'.format(body['type']) if 'text' in body: message += '.with_text("{}")'.format(body['text']) response += 'actions.append({})'.format(message) elif action['type'] == 'state': if body == state: continue response += 'actions.append(StateChange("{}"))'.format(body) state = body response += "\n" return response
def accept_schedule_callback(interface_name, uid, callback_name): from golem.core.dialog_manager import DialogManager db = get_redis() active_time = float(db.hget('session_active', uid).decode('utf-8')) inactive_seconds = time.time() - active_time interface = create_from_name(interface_name) print('{} from {} was active {}'.format(uid, interface, active_time)) parsed = { 'type' : 'schedule', 'entities' : { 'intent' : '_schedule', '_inactive_seconds' : inactive_seconds, '_callback_name' : callback_name } } dialog = DialogManager(uid=uid, interface=interface) _process_message(dialog, parsed)
def post_message(uid, chat_id, response): if uid and not chat_id: # TODO initiate conversation return message_id = get_redis().get('chat_message_id:{}'.format(chat_id)) if message_id: message_id = message_id.decode() bot_id = MicrosoftInterface.get_bot_id(chat_id) payload = { "type": "message", "from": { "id": bot_id }, "conversation": { "id": chat_id }, "recipient": { "id": uid }, "replyToId": message_id } payload.update(MicrosoftAdapter(chat_id).to_response(response)) url = MicrosoftInterface.get_base_url( chat_id) + 'conversations/' + chat_id + '/activities' if message_id and False: # replying to message TODO get and clear atomically or set timeout or something url += '/' + message_id headers = { "Authorization": "Bearer " + MicrosoftInterface.get_auth_token(), "Content-Type": "application/json" } logging.warning(url) logging.warning(payload) response = requests.post(url, data=json.dumps(payload), headers=headers) if response.status_code != 200: logging.warning(str(payload)) logging.warning(response) logging.warning(response.text) response.raise_for_status()
def get_base_url(chat_id) -> Optional[str]: """ Loads the correct endpoint url for a chat from redis. :param chat_id: Id of the chat. :return: url, throws Exception if chat_id not valid or url not found """ if not chat_id: raise Exception('Chat id must not be null') redis = get_redis() url = redis.get('ms_service_url_' + str(chat_id)) if url is None: raise Exception('Service url not found for chat id ' + str(chat_id)) url = url.decode() if not url.endswith('/'): url += '/' url += 'v3/' return url
def accept_schedule_callback(session: dict, callback_name): session = ChatSession.from_json(session) from golem.core.dialog_manager import DialogManager db = get_redis() active_time = float( db.hget('session_active', session.chat_id).decode('utf-8')) inactive_seconds = time.time() - active_time print('{} from {} was active {}'.format(session.chat_id, session.interface, active_time)) parsed = { 'type': 'schedule', 'entities': { 'intent': '_schedule', '_inactive_seconds': inactive_seconds, '_callback_name': callback_name } } dialog = DialogManager(session) _process_message(dialog, parsed)
def fake_move_to_state(chat_id, state: str, entities=()): # TODO solve chat session saving and restoring session = get_redis().hget("chat_session", chat_id) if not (session and state): logging.warning("ChatSession or State is null") return import json session = json.loads(session.decode('utf-8')) session = ChatSession.from_json(session) state = str(state) from golem.core.dialog_manager import DialogManager logger.debug("Moving chat id {} to state {}".format( session.chat_id, state)) dialog = DialogManager(session) msg_data = {'_state': state} for k, v in entities: msg_data[k] = [{"value": v}] dialog.process('schedule', msg_data)
def test(request): db = get_redis() results = db.hgetall('test_results') results = [ json.loads(results[k].decode('utf-8')) for k in sorted(list(results)) ] if results else [] updated_time = db.get('test_time') updated_time = db.get('test_time').decode( 'utf-8') if updated_time else None status = 'passed' avg = {'duration': 0, 'total': 0, 'init': 0, 'parsing': 0, 'processing': 0} passed = 0 for test in results: result = test['result'] if result['status'] == 'passed': passed += 1 avg['duration'] += result['duration'] avg['total'] += result['report']['avg']['total'] avg['init'] += result['report']['avg']['init'] avg['parsing'] += result['report']['avg']['parsing'] avg['processing'] += result['report']['avg']['processing'] elif status != 'exception': status = result['status'] if passed > 0: for key in avg: avg[key] = avg[key] / passed context = { 'tests': results, 'avg': avg, 'status': status, 'updated_time': updated_time } template = loader.get_template('golem/test.html') return HttpResponse(template.render(context, request))
def get_auth_token(): """ :returns: Auth token for Microsoft Bot API. """ redis = get_redis() if redis.exists('ms_token'): return redis.get('ms_token').decode() else: url = 'https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token' headers = {"Content-Type": "application/x-www-form-urlencoded"} payload = ('grant_type=client_credentials' + '&client_id=' + settings.GOLEM_CONFIG.get('MS_BOT_ID') + '&client_secret=' + settings.GOLEM_CONFIG.get('MS_BOT_TOKEN') + '&scope=' + 'https://api.botframework.com/.default') response = requests.post(url, data=payload, headers=headers) if response.status_code != 200: logging.error(response.text) response.raise_for_status() auth_data = response.json() token = auth_data['access_token'] ex = auth_data['expires_in'] redis.set('ms_token', str(token), ex=ex) return token
def load_profile(user_id, page_id, cache=True): db = get_redis() key = 'fb_profile_' + user_id if not cache or not db.exists(key): logging.debug('Loading fb profile...') url = "https://graph.facebook.com/v2.6/" + user_id params = { 'fields': 'first_name,last_name,profile_pic,locale,timezone,gender', 'access_token': FacebookInterface.get_page_token(page_id) } res = requests.get(url, params=params) if not res.status_code == requests.codes.ok: logging.error("ERROR loading FB profile! Response: {}".format( res.text)) return {} db.set(key, json.dumps(res.json()), ex=3600 * 24 * 14) # save value, expire in 14 days return json.loads(db.get(key).decode('utf-8'))
def save_to_cache(self, text, entities): if self.cache and 'date_interval' not in entities: db = get_redis() self.log.debug('Caching wit key: {} = {}'.format(text, entities)) db.hset('wit_cache', text, pickle.dumps(entities))
def post_message(session, response): GoogleActionsInterface.messages.append(response) get_redis().set("response_for_{id}".format(id=session.chat_id), str(response)) # TODO
def record_state_change(state): print('Recording state change: {}'.format(state)) db = get_redis() db.lpush('test_actions', json.dumps({'type': 'state', 'body': state}))
def clear_wit_cache(self): if self.cache: self.log.debug('Clearing Wit cache...') db = get_redis() db.delete('wit_cache')