Beispiel #1
0
 def __init__(self, message: BaseMessage, register: Register):
     self.message = message
     self.platform = message.get_platform()
     self.conversation = message.get_conversation()
     self.user = message.get_user()
     self.stack = Stack(message.get_layers())
     self.register = register
     self.custom_content = {}
Beispiel #2
0
    def accept(self, stack: Stack):
        """
        Checks that the stack can be accepted according to the `PATTERNS`.

        If the pattern is found, then its name is stored in the `annotation`
        attribute of the stack.
        """

        for name, pattern in self.PATTERNS.items():
            if stack.match_exp(pattern):
                stack.annotation = name
                return True
        return False
Beispiel #3
0
    async def _send_text(self,
                         request: Request,
                         stack: Stack,
                         parse_mode: Optional[Text] = None):
        """
        Base function for sending text
        """

        parts = []
        chat_id = request.message.get_chat_id()

        for layer in stack.layers:
            if isinstance(layer, (lyr.Text, lyr.RawText, lyr.Markdown)):
                text = await render(layer.text, request)
                parts.append(text)

        for part in parts[:-1]:
            await self.call(
                'sendMessage',
                text=part,
                chat_id=chat_id,
            )

        msg = {
            'text': parts[-1],
            'chat_id': chat_id,
        }

        if parse_mode is not None:
            msg['parse_mode'] = parse_mode

        await set_reply_markup(msg, request, stack)

        if stack.has_layer(Reply):
            reply = stack.get_layer(Reply)
            if reply.message:
                msg['reply_to_message_id'] = reply.message['message_id']

        if stack.has_layer(Update):
            update = stack.get_layer(Update)

            if update.inline_message_id:
                msg['inline_message_id'] = update.inline_message_id
                del msg['chat_id']
            else:
                msg['message_id'] = update.message_id

            await self.call('editMessageText',
                            {'Bad Request: message is not modified'}, **msg)
        else:
            await self.call('sendMessage', **msg)
Beispiel #4
0
    async def _send_typing(self, request: Request, stack: Stack):
        """
        Send to Facebook typing indications
        """

        active = stack.get_layer(lyr.Typing).active

        msg = ujson.dumps({
            'recipient': {
                'id': request.conversation.fbid,
            },
            'sender_action':
            'typing_on' if active else 'typing_off',
        })

        headers = {
            'content-type': 'application/json',
        }

        params = {
            'access_token': self._access_token(request),
        }

        post = self.session.post(
            MESSAGES_ENDPOINT,
            params=params,
            data=msg,
            headers=headers,
        )

        logger.debug('Sending: %s', msg)

        async with post as r:
            await self._handle_fb_response(r)
Beispiel #5
0
    async def _send_generic_template(self, request: Request, stack: Stack):
        """
        Generates and send a generic template.
        """

        gt = stack.get_layer(lyr.FbGenericTemplate)

        # noinspection PyUnresolvedReferences
        payload = {
            'template_type': 'generic',
            'elements': [await e.serialize(request) for e in gt.elements],
            'sharable': gt.is_sharable(),
        }

        if gt.aspect_ratio:
            payload['image_aspect_ratio'] = gt.aspect_ratio.value

        msg = {
            'attachment': {
                'type': 'template',
                'payload': payload
            }
        }

        await self._send(request, msg)
Beispiel #6
0
    async def _send_sleep(self, request: Request, stack: Stack):
        """
        Sleep for the amount of time specified in the Sleep layer
        """

        duration = stack.get_layer(lyr.Sleep).duration
        await asyncio.sleep(duration)
Beispiel #7
0
class Request(object):
    """
    The request object is generated after each message. Its goal is to provide
    a comprehensive access to the received message and its context to be used
    by the transitions and the handlers.
    """
    def __init__(self, message: BaseMessage, register: Register):
        self.message = message
        self.platform = message.get_platform()
        self.conversation = message.get_conversation()
        self.user = message.get_user()
        self.stack = Stack(message.get_layers())
        self.register = register
        self.custom_content = {}

    async def transform(self):
        await self.stack.transform(self)

    def get_trans_reg(self, name: Text, default: Any = None) -> Any:
        """
        Convenience function to access the transition register of a specific
        kind.

        :param name: Name of the register you want to see
        :param default: What to return by default
        """

        tr = self.register.get(Register.TRANSITION, {})
        return tr.get(name, default)

    def has_layer(self, class_: Type[L], became: bool = True) -> bool:
        """
        Proxy to stack
        """
        return self.stack.has_layer(class_, became)

    def get_layer(self, class_: Type[L], became: bool = True) -> L:
        """
        Proxy to stack
        """
        return self.stack.get_layer(class_, became)

    def get_layers(self, class_: Type[L], became: bool = True) -> List[L]:
        """
        Proxy to stack
        """
        return self.stack.get_layers(class_, became)
Beispiel #8
0
    def send(self, stack: Layers):
        """
        Intercept any potential "AnswerCallbackQuery" before adding the stack
        to the output buffer.
        """

        if not isinstance(stack, Stack):
            stack = Stack(stack)

        if 'callback_query' in self._update and stack.has_layer(Update):
            layer = stack.get_layer(Update)

            try:
                msg = self._update['callback_query']['message']
            except KeyError:
                layer.inline_message_id = \
                    self._update['callback_query']['inline_message_id']
            else:
                layer.chat_id = msg['chat']['id']
                layer.message_id = msg['message_id']

        if stack.has_layer(AnswerCallbackQuery):
            self._acq = stack.get_layer(AnswerCallbackQuery)
            stack = Stack([
                l for l in stack.layers
                if not isinstance(l, AnswerCallbackQuery)
            ])

        if stack.has_layer(Reply):
            layer = stack.get_layer(Reply)

            if 'message' in self._update:
                layer.message = self._update['message']
            elif 'callback_query' in self._update:
                layer.message = self._update['callback_query']['message']

        if 'inline_query' in self._update \
                and stack.has_layer(AnswerInlineQuery):
            a = stack.get_layer(AnswerInlineQuery)
            a.inline_query_id = self._update['inline_query']['id']

        if stack.layers:
            return super(TelegramResponder, self).send(stack)
Beispiel #9
0
    def typify(self, stack: Stack) -> List[Stack]:
        """
        Appends a typing stack after the given stack, but only if required
        (aka don't have two typing layers following each other).
        """

        if len(stack.layers) == 1 and isinstance(stack.layers[0], lyr.Typing):
            return [stack]

        return [stack, Stack([lyr.Typing()])]
Beispiel #10
0
    async def flush(self, request: Request, stacks: List[Stack]):
        """
        For all stacks to be sent, append a pause after each text layer.
        """

        ns = await self.expand_stacks(request, stacks)
        ns = self.split_stacks(ns)
        ns = self.clean_stacks(ns)

        await self.next(request, [Stack(x) for x in ns])
Beispiel #11
0
    def handle(self, *layers: BaseLayer):
        """
        Call this method to send a test message. Call it OUTSIDE the async
        loop. It will return when the message is fully handled.
        """

        self.sent = []
        stack = Stack(list(layers))
        message = TestMessage(stack)
        responder = TestResponder(self)

        run(self._notify(message, responder))
Beispiel #12
0
    async def _send_generic_template(self, request: Request, stack: Stack):
        """
        Generates and send a generic template.
        """

        gt = stack.get_layer(GenericTemplate)
        payload = await gt.serialize(request)

        msg = {'attachment': {'type': 'template', 'payload': payload}}

        await self._add_qr(stack, msg, request)
        await self._send(request, msg, stack)
Beispiel #13
0
    async def _send(self, request: Request, content: Dict[Text, Any],
                    stack: Stack):
        """
        Actually proceed to sending the message to the Facebook API.
        """

        msg = {
            'recipient': {
                'id': request.conversation.fbid,
            },
            'message': content,
        }

        if stack and stack.has_layer(MessagingType):
            mt = stack.get_layer(MessagingType)
        else:
            mt = MessagingType(response=True)

        msg.update(mt.serialize())
        msg_json = ujson.dumps(msg)

        headers = {
            'content-type': 'application/json',
        }

        params = {
            'access_token': self._access_token(request),
        }

        post = self.session.post(
            MESSAGES_ENDPOINT,
            params=params,
            data=msg_json,
            headers=headers,
        )

        logger.debug('Sending: %s', msg_json)

        async with post as r:
            await self._handle_fb_response(r)
Beispiel #14
0
    def send(self, stack: Layers):
        """
        Add a message stack to the send list.
        """

        if not isinstance(stack, Stack):
            stack = Stack(stack)

        if not self.platform.accept(stack):
            raise UnacceptableStack('The platform does not allow "{}"'.format(
                stack.describe()))

        self._stacks.append(stack)
Beispiel #15
0
    async def flush(self, request: Request, stacks: List[Stack]):
        """
        Add a typing stack after each stack.
        """

        ns: List[Stack] = []

        for stack in stacks:
            ns.extend(self.typify(stack))

        if len(ns) > 1 and ns[-1] == Stack([lyr.Typing()]):
            ns[-1].get_layer(lyr.Typing).active = False

        await self.next(request, ns)
Beispiel #16
0
    async def _send_typing(self, request: Request, stack: Stack):
        """
        In telegram, the typing stops when the message is received. Thus, there
        is no "typing stops" messages to send. The API is only called when
        typing must start.
        """

        t = stack.get_layer(lyr.Typing)

        if t.active:
            await self.call(
                'sendChatAction',
                chat_id=request.message.get_chat_id(),
                action='typing',
            )
Beispiel #17
0
    async def _send_button_template(self, request: Request, stack: Stack):
        """
        Generates and send a button template.
        """

        gt = stack.get_layer(ButtonTemplate)

        payload = {
            'template_type': 'button',
            'text': await render(gt.text, request),
            'buttons': [await b.serialize(request) for b in gt.buttons],
        }

        msg = {'attachment': {'type': 'template', 'payload': payload}}

        await self._add_qr(stack, msg, request)
        await self._send(request, msg, stack)
Beispiel #18
0
    async def _send_text(self, request: Request, stack: Stack):
        """
        Send text layers to the user. Each layer will go in its own bubble.

        Also, Facebook limits messages to 320 chars, so if any message is
        longer than that it will be split into as many messages as needed to
        be accepted by Facebook.
        """

        parts = []

        for layer in stack.layers:
            if isinstance(layer, (lyr.Text, lyr.RawText)):
                text = await render(layer.text, request)
                for part in wrap(text, 320):
                    parts.append(part)

        for part in parts[:-1]:
            await self._send(request, {
                'text': part,
            })

        part = parts[-1]

        msg = {
            'text': part,
        }

        try:
            qr = stack.get_layer(lyr.QuickRepliesList)
        except KeyError:
            pass
        else:
            # noinspection PyUnresolvedReferences
            msg['quick_replies'] = [
                await self._make_qr(o, request) for o in qr.options
            ]

        await self._send(request, msg)
Beispiel #19
0
 def __init__(self, message: 'BaseMessage'):
     from bernard.layers import Stack
     self.message = message
     self.stack: Stack = Stack(message.get_layers())
Beispiel #20
0
class Request(object):
    """
    The request object is generated after each message. Its goal is to provide
    a comprehensive access to the received message and its context to be used
    by the transitions and the handlers.
    """

    QUERY = 'query'
    HASH = 'hash'

    def __init__(self, message: BaseMessage, register: Register):
        self.message = message
        self.platform = message.get_platform()
        self.conversation = message.get_conversation()
        self.user = message.get_user()
        self.stack = Stack(message.get_layers())
        self.register = register
        self.custom_content = {}

        self._locale_override = None

    async def transform(self):
        await self.stack.transform(self)

    def get_trans_reg(self, name: Text, default: Any = None) -> Any:
        """
        Convenience function to access the transition register of a specific
        kind.

        :param name: Name of the register you want to see
        :param default: What to return by default
        """

        tr = self.register.get(Register.TRANSITION, {})
        return tr.get(name, default)

    def has_layer(self, class_: Type[L], became: bool = True) -> bool:
        """
        Proxy to stack
        """
        return self.stack.has_layer(class_, became)

    def get_layer(self, class_: Type[L], became: bool = True) -> L:
        """
        Proxy to stack
        """
        return self.stack.get_layer(class_, became)

    def get_layers(self, class_: Type[L], became: bool = True) -> List[L]:
        """
        Proxy to stack
        """
        return self.stack.get_layers(class_, became)

    def set_locale_override(self, locale: Text) -> None:
        """
        This allows to override manually the locale that will be used in
        replies.

        :param locale: Name of the locale (format 'fr' or 'fr_FR')
        """

        self._locale_override = locale

    async def get_locale(self) -> Text:
        """
        Get the locale to use for this request. It's either the overridden
        locale or the locale provided by the platform.

        :return: Locale to use for this request
        """

        if self._locale_override:
            return self._locale_override
        else:
            return await self.user.get_locale()

    async def get_trans_flags(self) -> 'Flags':
        """
        Gives a chance to middlewares to make the translation flags
        """

        from bernard.middleware import MiddlewareManager

        async def make_flags(request: Request) -> 'Flags':
            return {}

        mf = MiddlewareManager.instance().get('make_trans_flags', make_flags)
        return await mf(self)

    async def get_token(self) -> Text:
        """
        Returns the auth token from the message
        """

        return await self.message.get_token()

    async def sign_url(self, url, method=HASH):
        """
        Sign an URL with this request's auth token
        """

        token = await self.get_token()

        if method == self.QUERY:
            return patch_qs(url, {
                settings.WEBVIEW_TOKEN_KEY: token,
            })
        elif method == self.HASH:
            hash_id = 5
            p = list(urlparse(url))
            p[hash_id] = quote(token)
            return urlunparse(p)
        else:
            raise ValueError(f'Invalid signing method "{method}"')
Beispiel #21
0
 def __repr__(self):
     stack = Stack(self.get_layers())
     return f'{self.__class__.__name__}({stack})'
Beispiel #22
0
 async def _send_inline_answer(self, request: Request, stack: Stack):
     aiq = stack.get_layer(AnswerInlineQuery)
     answer = await aiq.serialize(request)
     await self.call('answerInlineQuery', **answer)