Ejemplo n.º 1
0
 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')
Ejemplo n.º 2
0
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'])
Ejemplo n.º 3
0
 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
Ejemplo n.º 4
0
    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()))
Ejemplo n.º 5
0
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()
Ejemplo n.º 6
0
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
Ejemplo n.º 7
0
 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]
Ejemplo n.º 8
0
 def __init__(self):
     super().__init__()
     self.verify_token = config.get_required('FB_VERIFY_TOKEN')
     self.pages = self._init_pages()
     self.adapter = FacebookAdapter()
Ejemplo n.º 9
0
 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')