def __init__(self): super().__init__() self.verify_token = config.get_required('FB_VERIFY_TOKEN') self.pages = self._init_pages() self.adapter = FacebookAdapter(self) self.profile_fields = config.get('FB_PROFILE_FIELDS', 'first_name,last_name,profile_pic')
def get_elastic(): from elasticsearch import Elasticsearch es_config = config.get_required('ELASTIC', 'ElasticSearch config not provided.') if not es_config: return None return Elasticsearch(es_config['HOST'], port=es_config['PORT'])
def _init_pages(self): page_configs = config.get_required('FB_PAGES') pages = [] for page_config in page_configs: name = page_config.get('NAME') if name is None: raise ValueError("FB_PAGES page property 'NAME' is missing.") token = page_config.get('TOKEN') if token is None: raise ValueError("FB_PAGES page '{}' property 'TOKEN' is missing.".format(name)) page_id = page_config.get('PAGE_ID') if page_id is None and len(page_configs) > 1: raise ValueError("FB_PAGES page '{}' property 'PAGE_ID' has to be specified " "when multiple pages are present.".format(name)) pages.append(MessengerPage(name=name, token=token, page_id=page_id)) return pages
def on_server_startup(self): from django.urls import reverse endpoint_url = self.base_url + 'setWebhook' deploy_url = config.get_required( 'DEPLOY_URL', "Telegram requires BOT_CONFIG.DEPLOY_URL to be set in order to register webhooks." ) deploy_url = deploy_url[:-1] if deploy_url[-1] == '/' else deploy_url callback_url = deploy_url + reverse('interface_webhook', args=['telegram']) logging.info('Reverse telegram URL is: {}'.format(callback_url)) payload = {'url': callback_url} response = requests.post(endpoint_url, data=payload) if not response.json()['ok']: raise Exception( "Error while registering telegram webhook: {}".format( response.json()))
class AlexaInterface(BotshotInterface): name = "alexa" greeting_intent = config.get_required( "GREETING_INTENT", 'BOT_CONFIG.GREETING_INTENT is required when using Alexa.') responses = {} def webhook(self, request): from botshot.core.chat_manager import ChatManager if request.method == "POST": manager = ChatManager() request_body = json.loads(request.body.decode('utf-8')) raw_message, entities = self.parse_raw_message(request_body) logging.info("Received raw message: %s", raw_message) self.responses[raw_message.raw_user_id] = [] manager.accept_with_entities(raw_message, entities) return self._generate_response(raw_message.raw_user_id) return HttpResponseBadRequest() def _parse_entities(self, slots): parsed = {} if not slots: return parsed for slot_name, entity_dict in slots.items(): entity_name = entity_dict['name'] utterance = entity_dict.get('value') source = "amazon_{}".format(entity_dict.get("source", "")) resolutions = entity_dict.get('resolutions', {}).get('resolutionsPerAuthority') if not resolutions: continue for resolution in resolutions: authority = resolution['authority'] for value_obj in resolution['values']: # ??!! value = value_obj['value']['id'] parsed.setdefault(entity_name, []).append({ "value": value, "slot": slot_name, "source": source, "authority": authority, "utterance": utterance, }) return parsed def parse_raw_message(self, request): print(request) req_data = request['request'] type = req_data['type'] user_id = request['session']['user']['userId'] is_new_session = request['session'].get('new', False) timestamp = time() # int(req_data['timestamp']) entities = {} if type == 'LaunchRequest': entities['intent'] = [{ "value": self.greeting_intent, "source": self.name, "confidence": 1.0 }] elif type == 'IntentRequest': intent = req_data['intent']['name'] if intent == "AMAZON.FallbackIntent" and is_new_session: entities['intent'] = [{ "value": self.greeting_intent, "source": self.name, "confidence": 1.0 }] else: entities['intent'] = [{ "value": intent, "source": self.name, "confidence": 1.0 }] parsed_entities = self._parse_entities( req_data['intent'].get('slots')) entities.update(parsed_entities) elif type == 'SessionEndedRequest': pass message = RawMessage(interface=self, raw_user_id=user_id, raw_conversation_id=user_id, conversation_meta={}, type=ChatMessage.MESSAGE, text=None, payload=None, timestamp=timestamp) return message, entities def _generate_response(self, user_id): payload = {"version": "1.0"} final_text = [] end_session = False for response in self.responses[user_id]: if isinstance(response, EndSessionResponse): end_session = True break elif isinstance(response, str): final_text.append(response) elif isinstance(response, TextMessage): final_text.append(response.as_string(humanize=True)) else: logging.warning( "Skipping response {} as it's unsupported in Alexa".format( response)) # TODO: what if there are no responses? payload['response'] = { "outputSpeech": { "type": "PlainText", "text": ' '.join(final_text) }, "shouldEndSession": end_session } return HttpResponse(content=json.dumps(payload)) def send_responses(self, conversation, reply_to, responses): responses_for_user = self.responses[conversation.raw_conversation_id] responses_for_user += responses def broadcast_responses(self, conversations, responses): raise NotImplementedError()
class TelegramInterface(BasicAsyncInterface): name = 'telegram' adapter = TelegramAdapter() base_url = 'https://api.telegram.org/bot{}/'.format( config.get_required('TELEGRAM_TOKEN')) should_hide_keyboard = config.get('TELEGRAM_HIDE_KEYBOARD', False) def on_server_startup(self): from django.urls import reverse endpoint_url = self.base_url + 'setWebhook' deploy_url = config.get_required( 'DEPLOY_URL', "Telegram requires BOT_CONFIG.DEPLOY_URL to be set in order to register webhooks." ) deploy_url = deploy_url[:-1] if deploy_url[-1] == '/' else deploy_url callback_url = deploy_url + reverse('interface_webhook', args=['telegram']) logging.info('Reverse telegram URL is: {}'.format(callback_url)) payload = {'url': callback_url} response = requests.post(endpoint_url, data=payload) if not response.json()['ok']: raise Exception( "Error while registering telegram webhook: {}".format( response.json())) def parse_raw_messages(self, request) -> Generator[RawMessage, None, None]: update_id = request[ 'update_id'] # TODO: use this counter for synchronization if 'message' in request: yield self._parse_raw_message(request['message']) elif 'channel_post' in request: yield self._parse_raw_message(request['channel_post']) elif 'callback_query' in request: # payload button clicked yield self._parse_callback_query(request['callback_query']) else: logging.warning("Ignoring unsupported telegram request") def _parse_raw_message(self, message): chat_id = message['chat']['id'] user_id = message.get('from', {}).get( 'id') # empty for messages sent to channels meta = {} date = message['date'] text = None if 'text' in message: text = message['text'] # UTF-8, 0-4096 characters else: logging.warning("Ignoring unsupported telegram message") return None return RawMessage(interface=self, raw_user_id=user_id, raw_conversation_id=chat_id, conversation_meta=meta, type=ChatMessage.MESSAGE, text=text, payload=None, timestamp=date) def _parse_callback_query(self, callback): query_id = callback['id'] user_id = callback['from']['id'] chat_instance_id = callback['chat_instance'] # this isn't chat_id message = callback.get('message') data_key = callback.get('data') # TODO: sanitize if not message: logging.warning( "Ignoring telegram callback without original message") return None if not data_key: logging.warning("Ignoring telegram callback without data") return None chat_id = message['chat']['id'] message_id = message['message_id'] data = self._retrieve_callback(data_key) data = json.loads(data) # answer query to hide loading bar in frontend url = self.base_url + 'answerCallbackQuery' payload = {'callback_query_id': query_id} response = requests.post(url, data=payload) if not response.json()['ok']: logging.error( 'Error occurred while answering to telegram callback: {}'. format(response.json())) # hide keyboard with buttons (not quick replies) if self.should_hide_keyboard: url = self.base_url + 'editMessageReplyMarkup' payload = { 'chat_id': chat_id, 'message_id': message_id, 'reply_markup': '' } response = requests.post(url, data=payload) if not response.json()['ok']: logging.error( 'Error occurred while hiding telegram keyboard: {}'.format( response.json())) return RawMessage(interface=self, raw_user_id=user_id, raw_conversation_id=chat_id, conversation_meta={}, type=ChatMessage.BUTTON, text=None, payload=data, timestamp=time()) @classmethod def _persist_callback(cls, 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 @classmethod def _retrieve_callback(cls, key) -> Optional[str]: redis = get_redis() if redis.exists(key): return redis.get(key).decode('utf8') return None def on_message_processing_start(self, message: ChatMessage): url = self.base_url + 'sendChatAction' payload = { 'chat_id': message.conversation.raw_conversation_id, 'action': 'typing' } response = requests.post(url, data=payload) if not response.json()['ok']: logging.warning( "Error occurred while sending Telegram typing action: {}". format(response.json())) def send_responses(self, conversation, reply_to, responses): messages = [] chat_id = conversation.raw_conversation_id for response in responses: messages += self.adapter.transform_message(response, chat_id) for method, payload in messages: url = self.base_url + method response = requests.post(url, data=payload) if not response.json()['ok']: logging.warning( "Telegram message for method {} failed to send: {}".format( url, response.json())) break def broadcast_responses(self, conversations, responses): for conversation in conversations: self.send_responses(conversation=conversation, reply_to=None, responses=responses) ######################################################################################################################## @staticmethod def has_message_expired(message: dict) -> bool: if not (message and 'date' in message): logging.warning('Invalid date in message') return True received = datetime.fromtimestamp(int(message['date'])) now = datetime.utcnow() if abs(now - received) > timedelta( seconds=settings.BOT_CONFIG['MSG_LIMIT_SECONDS']): logging.warning('Ignoring message, too old') return True return False
def get_interfaces(): """Returns a list of registered interface classes.""" # FIXME: avoid importing classes every time this is called # TODO: check that interfaces have the correct subclass and a name set interface_paths = config.get_required('INTERFACES') return [import_string(path) for path in interface_paths]
def __init__(self): super().__init__() self.verify_token = config.get_required('FB_VERIFY_TOKEN') self.pages = self._init_pages() self.adapter = FacebookAdapter()
def __init__(self): super().__init__() self.base_url = 'https://chatbase.com/api' self.api_key = config.get_required("CHATBASE_API_KEY", "Chatbase API key not provided!") self.bot_version = config.get("VERSION", 'no version')