RasaHQ/rasa_core

View on GitHub
rasa/core/channels/twilio.py

Summary

Maintainability
A
35 mins
Test Coverage
# -*- coding: utf-8 -*-
import logging
from sanic import Blueprint, response
from twilio.rest import Client

from rasa.core.channels import InputChannel
from rasa.core.channels import UserMessage, OutputChannel

logger = logging.getLogger(__name__)


class TwilioOutput(Client, OutputChannel):
    """Output channel for Twilio"""

    @classmethod
    def name(cls):
        return "twilio"

    def __init__(self, account_sid, auth_token, twilio_number):
        super(TwilioOutput, self).__init__(account_sid, auth_token)
        self.twilio_number = twilio_number
        self.send_retry = 0
        self.max_retry = 5

    async def send_text_message(self, recipient_id, text):
        """Sends text message"""

        for message_part in text.split("\n\n"):
            await self._send_text(recipient_id, message_part)

    async def _send_text(self, recipient_id, text):
        from twilio.base.exceptions import TwilioRestException

        message = None
        try:
            while not message and self.send_retry < self.max_retry:
                message = self.messages.create(body=text,
                                               to=recipient_id,
                                               from_=self.twilio_number)
                self.send_retry += 1
        except TwilioRestException as e:
            logger.error("Something went wrong " + repr(e.msg))
        finally:
            self.send_retry = 0

        if not message and self.send_retry == self.max_retry:
            logger.error("Failed to send message. Max number of "
                         "retires exceeded.")

        return message


class TwilioInput(InputChannel):
    """Twilio input channel"""

    @classmethod
    def name(cls):
        return "twilio"

    @classmethod
    def from_credentials(cls, credentials):
        if not credentials:
            cls.raise_missing_credentials_exception()

        return cls(credentials.get("account_sid"),
                   credentials.get("auth_token"),
                   credentials.get("twilio_number"))

    def __init__(self, account_sid, auth_token, twilio_number,
                 debug_mode=True):
        self.account_sid = account_sid
        self.auth_token = auth_token
        self.twilio_number = twilio_number
        self.debug_mode = debug_mode

    def blueprint(self, on_new_message):
        twilio_webhook = Blueprint('twilio_webhook', __name__)

        @twilio_webhook.route("/", methods=['GET'])
        async def health(request):
            return response.json({"status": "ok"})

        @twilio_webhook.route("/webhook", methods=['POST'])
        async def message(request):
            sender = request.values.get('From', None)
            text = request.values.get('Body', None)

            out_channel = TwilioOutput(self.account_sid, self.auth_token,
                                       self.twilio_number)

            if sender is not None and message is not None:
                try:
                    # @ signs get corrupted in SMSes by some carriers
                    text = text.replace('ยก', '@')
                    await on_new_message(UserMessage(text, out_channel, sender,
                                                     input_channel=self.name()))
                except Exception as e:
                    logger.error("Exception when trying to handle "
                                 "message.{0}".format(e))
                    logger.debug(e, exc_info=True)
                    if self.debug_mode:
                        raise
                    pass
            else:
                logger.debug("Invalid message")

            return response.text("success")

        return twilio_webhook