Ejemplo n.º 1
0
    def handle_outbound_message(self, message):
        if random.random() > self.ack_rate:
            yield self.publish_nack(message['message_id'],
                'Not accepted by network')
            return

        dr = ('failed' if random.random() < self.failure_rate
                else 'delivered')
        log.info('MT %(dr)s: %(from_addr)s -> %(to_addr)s: %(content)s' % {
            'dr': dr,
            'from_addr': message['from_addr'],
            'to_addr': message['to_addr'],
            'content': message['content'],
            })
        yield self.publish_ack(message['message_id'],
            sent_message_id=uuid.uuid4().hex)
        yield self.publish_delivery_report(message['message_id'], dr)
        if random.random() < self.reply_rate:
            reply_copy = self.reply_copy or message['content']
            log.info('MO %(from_addr)s -> %(to_addr)s: %(content)s' % {
                'from_addr': message['to_addr'],
                'to_addr': message['from_addr'],
                'content': reply_copy,
                })
            yield self.publish_message(
                message_id=uuid.uuid4().hex,
                content=reply_copy,
                to_addr=message['from_addr'],
                from_addr=message['to_addr'],
                provider='devnull',
                session_event=TransportUserMessage.SESSION_NONE,
                transport_type=self.transport_type,
                transport_metadata={})
Ejemplo n.º 2
0
    def process_command_send_message(self, user_account_key, conversation_key,
                                     command_data, **kwargs):
        if kwargs:
            log.info("Received unexpected command args: %s" % (kwargs,))
        conv = yield self.get_conversation(user_account_key, conversation_key)
        if conv is None:
            log.warning("Cannot find conversation '%s' for user '%s'." % (
                conversation_key, user_account_key))
            return

        log.info('Processing send_message: %s' % kwargs)
        to_addr = command_data['to_addr']
        content = command_data['content']
        msg_options = command_data['msg_options']
        in_reply_to = msg_options.pop('in_reply_to', None)
        self.add_conv_to_msg_options(conv, msg_options)
        if in_reply_to:
            msg = yield self.vumi_api.mdb.get_inbound_message(in_reply_to)
            if msg:
                yield self.reply_to(
                    msg, content,
                    helper_metadata=msg_options['helper_metadata'])
            else:
                log.warning('Unable to reply, message %s does not exist.' % (
                    in_reply_to))
        else:
            yield self.send_to(
                to_addr, content, endpoint='default', **msg_options)
Ejemplo n.º 3
0
 def send_event_to_client(self, event, conversation, push_url):
     if push_url is None:
         log.info(
             "push_event_url not configured for conversation: %s" % (
                 conversation.key))
         return
     return self.push(push_url, event)
Ejemplo n.º 4
0
    def process_command_send_message(self, user_account_key, conversation_key,
                                     command_data, **kwargs):
        if kwargs:
            log.info("Received unexpected command args: %s" % (kwargs, ))
        conv = yield self.get_conversation(user_account_key, conversation_key)
        if conv is None:
            log.warning("Cannot find conversation '%s' for user '%s'." %
                        (conversation_key, user_account_key))
            return

        log.info('Processing send_message: %s' % kwargs)
        to_addr = command_data['to_addr']
        content = command_data['content']
        msg_options = command_data['msg_options']
        in_reply_to = msg_options.pop('in_reply_to', None)
        self.add_conv_to_msg_options(conv, msg_options)
        if in_reply_to:
            msg = yield self.vumi_api.mdb.get_inbound_message(in_reply_to)
            if msg:
                yield self.reply_to(
                    msg,
                    content,
                    helper_metadata=msg_options['helper_metadata'])
            else:
                log.warning('Unable to reply, message %s does not exist.' %
                            (in_reply_to))
        else:
            yield self.send_to(to_addr,
                               content,
                               endpoint='default',
                               **msg_options)
Ejemplo n.º 5
0
 def send_message(self, batch_id, to_addr, content, msg_options):
     msg = yield self.send_to(to_addr,
                              content,
                              endpoint='default',
                              **msg_options)
     yield self.vumi_api.mdb.add_outbound_message(msg, batch_id=batch_id)
     log.info('Stored outbound %s' % (msg, ))
Ejemplo n.º 6
0
    def send_scheduled_messages(self, conv):
        config = self.get_config_for_conversation(conv)
        messages = config.messages
        message_options = {}
        conv.set_go_helper_metadata(
            message_options.setdefault('helper_metadata', {}))

        for contacts in (yield conv.get_opted_in_contact_bunches(
                conv.delivery_class)):
            for contact in (yield contacts):
                index_key = 'scheduled_message_index_%s' % (conv.key, )
                message_index = int(contact.extra[index_key] or '0')
                if message_index >= len(messages):
                    # We have nothing more to send to this person.
                    continue

                to_addr = contact.addr_for(conv.delivery_class)
                if not to_addr:
                    log.info("No suitable address found for contact %s %r" % (
                        contact.key,
                        contact,
                    ))
                    continue

                yield self.send_message(conv.batch.key, to_addr,
                                        messages[message_index],
                                        message_options)

                contact.extra[index_key] = u'%s' % (message_index + 1)
                yield contact.save()
Ejemplo n.º 7
0
    def send_scheduled_messages(self, conv):
        config = self.get_config_for_conversation(conv)
        messages = config.messages
        message_options = {}
        conv.set_go_helper_metadata(
            message_options.setdefault('helper_metadata', {}))

        for contacts in (yield conv.get_opted_in_contact_bunches(
                conv.delivery_class)):
            for contact in (yield contacts):
                index_key = 'scheduled_message_index_%s' % (conv.key,)
                message_index = int(contact.extra[index_key] or '0')
                if message_index >= len(messages):
                    # We have nothing more to send to this person.
                    continue

                to_addr = contact.addr_for(conv.delivery_class)
                if not to_addr:
                    log.info("No suitable address found for contact %s %r" % (
                        contact.key, contact,))
                    continue

                yield self.send_message(
                    conv.batch.key, to_addr, messages[message_index],
                    message_options)

                contact.extra[index_key] = u'%s' % (message_index + 1)
                yield contact.save()
Ejemplo n.º 8
0
    def handle_send_to_endpoint(self, api, command):
        """
        Sends a message to a specified endpoint.

        Command fields:
            - ``content``: The body of the reply message.
            - ``to_addr``: The address of the recipient (e.g. an MSISDN).
            - ``endpoint``: The name of the endpoint to send the message via.

        Reply fields:
            - ``success``: ``true`` if the operation was successful, otherwise
              ``false``.

        Example:

        .. code-block:: javascript

            api.request(
                'outbound.send_to_endpoint',
                {content: 'Welcome!', to_addr: '+27831234567',
                 endpoint: 'sms'},
                function(reply) { api.log_info('Message sent: ' +
                                               reply.success); });
        """
        if not 'content' in command:
            return self._mkfaild(
                command, reason=u"'content' must be given in sends.")
        if not isinstance(command['content'], (unicode, type(None))):
            return self._mkfaild(
                command, reason=u"'content' must be unicode or null.")
        if not isinstance(command.get('endpoint'), unicode):
            return self._mkfaild(
                command, reason=u"'endpoint' must be given in sends.")
        if not isinstance(command.get('to_addr'), unicode):
            return self._mkfaild(
                command, reason=u"'to_addr' must be given in sends.")

        endpoint = command['endpoint']
        content = command['content']
        to_addr = command['to_addr']

        conv = self.app_worker.conversation_for_api(api)
        if endpoint not in conv.extra_endpoints:
            return self._mkfaild(
                command, reason="Endpoint %r not configured" % (endpoint,))

        msg_options = {}
        self.app_worker.add_conv_to_msg_options(conv, msg_options)

        log.info("Sending outbound message to %r via endpoint %r, content: %r"
                 % (to_addr, endpoint, content))

        d = self.app_worker.send_to(
            to_addr, content, endpoint=endpoint, **msg_options)

        d.addCallback(lambda r: self.reply(command, success=True))
        d.addErrback(lambda f: self._mkfail(command,
                                            unicode(f.getErrorMessage())))
        return d
Ejemplo n.º 9
0
 def consume_control_command(self, cmd):
     worker_name = cmd.get('worker_name')
     publisher = self.worker_publishers.get(worker_name)
     if publisher:
         yield publisher.publish_message(cmd)
         log.info('Sent %s to %s' % (cmd, worker_name))
     else:
         log.error('No worker publisher available for %s' % (cmd, ))
Ejemplo n.º 10
0
 def consume_control_command(self, cmd):
     worker_name = cmd.get('worker_name')
     publisher = self.worker_publishers.get(worker_name)
     if publisher:
         yield publisher.publish_message(cmd)
         log.info('Sent %s to %s' % (cmd, worker_name))
     else:
         log.error('No worker publisher available for %s' % (cmd,))
Ejemplo n.º 11
0
    def handle_send_to_tag(self, api, command):
        """
        Sends a message to a specified tag.

        Command fields:
            - ``content``: The body of the reply message.
            - ``to_addr``: The address of the recipient (e.g. an MSISDN).
            - ``tagpool``: The name of the tagpool to send the message via.
            - ``tag``: The name of the tag (within the tagpool) to send the
              message from. Your Go user account must have the tag acquired.

        Reply fields:
            - ``success``: ``true`` if the operation was successful, otherwise
              ``false``.

        Example:

        .. code-block:: javascript

            api.request(
                'outbound.send_to_tag',
                {content: 'Welcome!', to_addr: '+27831234567',
                 tagpool: 'vumi_long', tag: 'default10001'},
                function(reply) { api.log_info('Message sent: ' +
                                               reply.success); });
        """
        tag = (command.get('tagpool'), command.get('tag'))
        content = command.get('content')
        to_addr = command.get('to_addr')
        if any(not isinstance(u, unicode)
               for u in (tag[0], tag[1], content, to_addr)):
            returnValue(
                self._mkfail(command,
                             reason="Tag, content or to_addr not specified"))
        log.info("Sending outbound message to %r via tag %r, content: %r" %
                 (to_addr, tag, content))

        conv = self.app_worker.conversation_for_api(api)
        tags = [
            tuple(endpoint.split(":", 1)) for endpoint in conv.extra_endpoints
        ]
        if tag not in tags:
            returnValue(
                self._mkfail(command,
                             reason="Tag %r not held by account" % (tag, )))

        msg_options = {}
        self.app_worker.add_conv_to_msg_options(conv, msg_options)
        endpoint = ':'.join(tag)
        yield self.app_worker.send_to(to_addr,
                                      content,
                                      endpoint=endpoint,
                                      **msg_options)

        returnValue(self.reply(command, success=True))
Ejemplo n.º 12
0
 def setup_transport(self):
     config = self.get_static_config()
     log.info('Starting ParlayX transport: %s' % (self.transport_name,))
     self.web_resource = yield self.start_web_resources(
         [(SmsNotificationService(self.handle_raw_inbound_message,
                                  self.publish_delivery_report),
           config.web_notification_path)],
         config.web_notification_port)
     self._parlayx_client = self._create_client(config)
     if config.start_notifications:
         yield self._parlayx_client.start_sms_notification()
Ejemplo n.º 13
0
 def add_entry(self, src_conn, src_endpoint, dst_conn, dst_endpoint):
     src_conn = _to_conn(src_conn)
     dst_conn = _to_conn(dst_conn)
     self.validate_entry(src_conn, src_endpoint, dst_conn, dst_endpoint)
     connector_dict = self._routing_table.setdefault(str(src_conn), {})
     if src_endpoint in connector_dict:
         log.info(
             "Replacing routing entry for (%r, %r): was %r, now %r" % (
                 str(src_conn), src_endpoint, connector_dict[src_endpoint],
                 [str(dst_conn), dst_endpoint]))
     connector_dict[src_endpoint] = [str(dst_conn), dst_endpoint]
Ejemplo n.º 14
0
 def process_command_reconcile_cache(self, conversation_key,
                                     user_account_key):
     key_tuple = (conversation_key, user_account_key)
     if key_tuple in self._cache_recon_conversations:
         log.info("Ignoring conversation %s for user %s because the "
                  "previous cache recon run is still going." %
                  (conversation_key, user_account_key))
         return
     self._cache_recon_conversations.add(key_tuple)
     user_api = self.get_user_api(user_account_key)
     yield self.reconcile_cache(user_api, conversation_key)
     self._cache_recon_conversations.remove(key_tuple)
Ejemplo n.º 15
0
 def process_command_reconcile_cache(self, conversation_key,
                                     user_account_key):
     key_tuple = (conversation_key, user_account_key)
     if key_tuple in self._cache_recon_conversations:
         log.info("Ignoring conversation %s for user %s because the "
                  "previous cache recon run is still going." % (
                      conversation_key, user_account_key))
         return
     self._cache_recon_conversations.add(key_tuple)
     user_api = self.get_user_api(user_account_key)
     yield self.reconcile_cache(user_api, conversation_key)
     self._cache_recon_conversations.remove(key_tuple)
Ejemplo n.º 16
0
    def handle_raw_inbound_message(self, message_id, request):
        request_data = self.get_request_data_dict(request)

        values, errors = self.get_field_values(
            request_data,
            self.EXPECTED_FIELDS,
            self.OPTIONAL_FIELDS,  # pass this in for error checking
        )

        optional_values = self.get_optional_field_values(
            request_data,
            self.OPTIONAL_FIELDS
        )

        if errors:
            log.info('Unhappy incoming message: %s ' % (errors,))
            yield self.finish_request(
                message_id, json.dumps(errors), code=http.BAD_REQUEST
            )
            return

        if values['type'] == self.REQUEST_TYPE['response']:  # resume session
            session_event = TransportUserMessage.SESSION_RESUME
        elif values['type'] == self.REQUEST_TYPE['request']:  # new session
            session_event = TransportUserMessage.SESSION_NEW
        else:
            # TODO #4: handle self.REQUEST_TYPE['release'] and
            # self.REQUEST_TYPE['timeout'].
            # Default to new session for now.
            session_event = TransportUserMessage.SESSION_NEW

        if optional_values['msg'] is not None:
            content = optional_values['msg']
        else:
            content = None

        log.info(
            'BlastSMSUssdTransport receiving inbound message from %s to '
            '%s.' % (values['msisdn'], values['shortcode']))

        yield self.publish_message(
            message_id=message_id,
            content=content,
            to_addr=values['shortcode'],
            from_addr=values['msisdn'],
            session_event=session_event,
            transport_type=self.TRANSPORT_TYPE,
            transport_name=self.TRANSPORT_NAME,
            transport_metadata={
                'sessionid': values['sessionid'],
                'appid': optional_values['appid'],
            },
        )
Ejemplo n.º 17
0
    def handle_event(self, event, handler_config):
        """
        From an account and conversation, this finds a batch and a tag.
        While there could be multiple batches per conversation and
        multiple tags per batch, this assumes lists of one, and takes
        the first entry of each list

        TODO: Turn this into a generic API Command Sender?

        An Example of a event_dispatcher.yaml config file, with mapped
        conversations in the config:

            transport_name: event_dispatcher
            event_handlers:
                sign_up_handler: go.vumitools.handler.SendMessageCommandHandler
            account_handler_configs:
                '73ad76ec8c2e40858dc9d6b934049d95':
                - - ['a6a20571e77f4aa89a8b10a771b005bc', sign_up]
                  - - [ sign_up_handler, {
                      worker_name: 'multi_survey_application',
                      conversation_key: 'a6a20571e77f4aa89a8b10a771b005bc'
                      }
                  ]
        """

        log.info(
            "SendMessageCommandHandler handling event: %s with config: %s" %
            (event, handler_config))

        user_api = self.get_user_api(event.payload['account_key'])
        conv = yield user_api.conversation_store.get_conversation_by_key(
            event.payload['conversation_key'])
        conv = user_api.wrap_conversation(conv)

        command_data = event.payload['content']
        command_data['batch_id'] = conv.batch.key
        command_data['msg_options'] = {
            'helper_metadata': {
                'go': {
                    'user_account': event.payload['account_key']
                },
            },
        }

        sm_cmd = VumiApiCommand.command(
            handler_config['worker_name'],
            "send_message",
            command_data=command_data,
            conversation_key=handler_config['conversation_key'],
            account_key=event.payload['account_key'])
        log.info("Publishing command: %s" % sm_cmd)
        self.dispatcher.api_command_publisher.publish_message(sm_cmd)
Ejemplo n.º 18
0
 def create_wechat_menu(self, access_token, menu_structure):
     url = self.make_url("menu/create", {"access_token": access_token})
     response = yield http_request_full(
         url, method="POST", data=json.dumps(menu_structure), headers={"Content-Type": ["application/json"]}
     )
     if not http_ok(response):
         raise WeChatApiException("Received HTTP code: %r when creating the menu." % (response.code,))
     data = json.loads(response.delivered_body)
     if data["errcode"] != 0:
         raise WeChatApiException(
             "Received errcode: %(errcode)s, errmsg: %(errmsg)s " "when creating WeChat Menu." % data
         )
     log.info("WeChat Menu created succesfully.")
Ejemplo n.º 19
0
    def handle_raw_inbound_message(self, message_id, request):
        values, errors = self.get_field_values(
            request,
            self.EXPECTED_FIELDS,
            self.OPTIONAL_FIELDS,
        )

        optional_values = self.get_optional_field_values(
            request,
            self.OPTIONAL_FIELDS
        )

        if errors:
            log.info('Unhappy incoming message: %s ' % (errors,))
            yield self.finish_request(
                message_id, json.dumps(errors), code=http.BAD_REQUEST
            )
            return

        from_addr = values['msisdn']
        provider = self.normalise_provider(values['provider'])
        ussd_session_id = optional_values['ussdSessionId']

        if optional_values['to_addr'] is not None:
            session_event = TransportUserMessage.SESSION_RESUME
            to_addr = optional_values['to_addr']
            content = optional_values['request']
        else:
            session_event = TransportUserMessage.SESSION_NEW
            to_addr = optional_values['request']
            content = None

        log.info(
            'AatUssdTransport receiving inbound message from %s to %s.' % (
                from_addr, to_addr))

        yield self.publish_message(
            message_id=message_id,
            content=content,
            to_addr=to_addr,
            from_addr=from_addr,
            session_event=session_event,
            transport_type=self.transport_type,
            transport_metadata={
                'aat_ussd': {
                    'provider': provider,
                    'ussd_session_id': ussd_session_id,
                }
            },
            provider=provider,
        )
Ejemplo n.º 20
0
    def handle_event(self, event, handler_config):
        """
        From an account and conversation, this finds a batch and a tag.
        While there could be multiple batches per conversation and
        multiple tags per batch, this assumes lists of one, and takes
        the first entry of each list

        TODO: Turn this into a generic API Command Sender?

        An Example of a event_dispatcher.yaml config file, with mapped
        conversations in the config:

            transport_name: event_dispatcher
            event_handlers:
                sign_up_handler: go.vumitools.handler.SendMessageCommandHandler
            account_handler_configs:
                '73ad76ec8c2e40858dc9d6b934049d95':
                - - ['a6a20571e77f4aa89a8b10a771b005bc', sign_up]
                  - - [ sign_up_handler, {
                      worker_name: 'multi_survey_application',
                      conversation_key: 'a6a20571e77f4aa89a8b10a771b005bc'
                      }
                  ]
        """

        log.info(
            "SendMessageCommandHandler handling event: %s with config: %s" % (
            event, handler_config))

        user_api = self.get_user_api(event.payload['account_key'])
        conv = yield user_api.conversation_store.get_conversation_by_key(
            event.payload['conversation_key'])
        conv = user_api.wrap_conversation(conv)

        command_data = event.payload['content']
        command_data['batch_id'] = conv.batch.key
        command_data['msg_options'] = {
            'helper_metadata': {
                'go': {'user_account': event.payload['account_key']},
            },
        }

        sm_cmd = VumiApiCommand.command(
                handler_config['worker_name'],
                "send_message",
                command_data=command_data,
                conversation_key=handler_config['conversation_key'],
                account_key=event.payload['account_key']
                )
        log.info("Publishing command: %s" % sm_cmd)
        self.dispatcher.api_command_publisher.publish_message(sm_cmd)
Ejemplo n.º 21
0
    def handle_send_to_tag(self, api, command):
        """
        Sends a message to a specified tag.

        Command fields:
            - ``content``: The body of the reply message.
            - ``to_addr``: The address of the recipient (e.g. an MSISDN).
            - ``tagpool``: The name of the tagpool to send the message via.
            - ``tag``: The name of the tag (within the tagpool) to send the
              message from. Your Go user account must have the tag acquired.

        Reply fields:
            - ``success``: ``true`` if the operation was successful, otherwise
              ``false``.

        Example:

        .. code-block:: javascript

            api.request(
                'outbound.send_to_tag',
                {content: 'Welcome!', to_addr: '+27831234567',
                 tagpool: 'vumi_long', tag: 'default10001'},
                function(reply) { api.log_info('Message sent: ' +
                                               reply.success); });
        """
        tag = (command.get('tagpool'), command.get('tag'))
        content = command.get('content')
        to_addr = command.get('to_addr')
        if any(not isinstance(u, unicode)
               for u in (tag[0], tag[1], content, to_addr)):
            returnValue(self._mkfail(
                command, reason="Tag, content or to_addr not specified"))
        log.info("Sending outbound message to %r via tag %r, content: %r" %
                 (to_addr, tag, content))

        conv = self.app_worker.conversation_for_api(api)
        tags = [tuple(endpoint.split(":", 1))
                for endpoint in conv.extra_endpoints]
        if tag not in tags:
            returnValue(self._mkfail(
                command, reason="Tag %r not held by account" % (tag,)))

        msg_options = {}
        self.app_worker.add_conv_to_msg_options(conv, msg_options)
        endpoint = ':'.join(tag)
        yield self.app_worker.send_to(
            to_addr, content, endpoint=endpoint, **msg_options)

        returnValue(self.reply(command, success=True))
Ejemplo n.º 22
0
 def create_wechat_menu(self, access_token, menu_structure):
     url = self.make_url('menu/create', {'access_token': access_token})
     response = yield self.http_request_full(
         url, method='POST', data=json.dumps(menu_structure),
         headers={'Content-Type': ['application/json']})
     if not http_ok(response):
         raise WeChatApiException(
             'Received HTTP code: %r when creating the menu.' % (
                 response.code,))
     data = json.loads(response.delivered_body)
     if data['errcode'] != 0:
         raise WeChatApiException(
             'Received errcode: %(errcode)s, errmsg: %(errmsg)s '
             'when creating WeChat Menu.' % data)
     log.info('WeChat Menu created succesfully.')
Ejemplo n.º 23
0
 def handle_raw_inbound_message(self, correlator, linkid, inbound_message):
     """
     Handle incoming text messages from `SmsNotificationService` callbacks.
     """
     log.info('Receiving SMS via ParlayX: %r: %r' % (
         correlator, inbound_message,))
     message_id = extract_message_id(correlator)
     return self.publish_message(
         message_id=message_id,
         content=inbound_message.message,
         to_addr=inbound_message.service_activation_number,
         from_addr=inbound_message.sender_address,
         provider='parlayx',
         transport_type=self.transport_type,
         transport_metadata=dict(linkid=linkid))
Ejemplo n.º 24
0
 def process_command_stop(self, user_account_key, router_key):
     log.info("Stopping router '%s' for user '%s'." %
              (router_key, user_account_key))
     router = yield self.get_router(user_account_key, router_key)
     if router is None:
         log.warning("Trying to stop missing router '%s' for user '%s'." %
                     (router_key, user_account_key))
         return
     if not router.stopping():
         log.warning(
             "Trying to stop router '%s' for user '%s' with invalid "
             "status: %s" % (router_key, user_account_key, router.status))
         return
     router.set_status_stopped()
     yield router.save()
Ejemplo n.º 25
0
 def handle_outbound_message(self, message):
     """
     Send a text message via the ParlayX client.
     """
     log.info('Sending SMS via ParlayX: %r' % (message.to_json(),))
     transport_metadata = message.get('transport_metadata', {})
     d = self._parlayx_client.send_sms(
         message['to_addr'],
         message['content'],
         unique_correlator(message['message_id']),
         transport_metadata.get('linkid'))
     d.addErrback(self.handle_outbound_message_failure, message)
     d.addCallback(
         lambda requestIdentifier: self.publish_ack(
             message['message_id'], requestIdentifier))
     return d
Ejemplo n.º 26
0
 def process_command_stop(self, user_account_key, router_key):
     log.info("Stopping router '%s' for user '%s'." % (
         router_key, user_account_key))
     router = yield self.get_router(user_account_key, router_key)
     if router is None:
         log.warning(
             "Trying to stop missing router '%s' for user '%s'." % (
                 router_key, user_account_key))
         return
     if not router.stopping():
         log.warning(
             "Trying to stop router '%s' for user '%s' with invalid "
             "status: %s" % (router_key, user_account_key, router.status))
         return
     router.set_status_stopped()
     yield router.save()
Ejemplo n.º 27
0
 def create_wechat_menu(self, access_token, menu_structure):
     url = self.make_url('menu/create', {'access_token': access_token})
     response = yield self.http_request_full(
         url,
         method='POST',
         data=json.dumps(menu_structure),
         headers={'Content-Type': ['application/json']})
     if not http_ok(response):
         raise WeChatApiException(
             'Received HTTP code: %r when creating the menu.' %
             (response.code, ))
     data = json.loads(response.delivered_body)
     if data['errcode'] != 0:
         raise WeChatApiException(
             'Received errcode: %(errcode)s, errmsg: %(errmsg)s '
             'when creating WeChat Menu.' % data)
     log.info('WeChat Menu created succesfully.')
Ejemplo n.º 28
0
 def process_command_start(self, user_account_key, conversation_key):
     log.info("Starting conversation '%s' for user '%s'." %
              (conversation_key, user_account_key))
     conv = yield self.get_conversation(user_account_key, conversation_key)
     if conv is None:
         log.warning(
             "Trying to start missing conversation '%s' for user '%s'." %
             (conversation_key, user_account_key))
         return
     if not conv.starting():
         status = conv.get_status()
         log.warning(
             "Trying to start conversation '%s' for user '%s' with invalid "
             "status: %s" % (conversation_key, user_account_key, status))
         return
     conv.set_status_started()
     yield conv.save()
Ejemplo n.º 29
0
 def process_command_start(self, user_account_key, conversation_key):
     log.info("Starting conversation '%s' for user '%s'." % (
         conversation_key, user_account_key))
     conv = yield self.get_conversation(user_account_key, conversation_key)
     if conv is None:
         log.warning(
             "Trying to start missing conversation '%s' for user '%s'." % (
                 conversation_key, user_account_key))
         return
     if not conv.starting():
         status = conv.get_status()
         log.warning(
             "Trying to start conversation '%s' for user '%s' with invalid "
             "status: %s" % (conversation_key, user_account_key, status))
         return
     conv.set_status_started()
     yield conv.save()
Ejemplo n.º 30
0
 def _call_rapidsms(self, message):
     config = yield self.get_config(message)
     http_method = config.rapidsms_http_method.encode("utf-8")
     headers = self.get_auth_headers(config)
     yield self._store_message(message, config.vumi_reply_timeout)
     response = http_request_full(config.rapidsms_url.geturl(), message.to_json(), headers, http_method)
     response.addCallback(lambda response: log.info(response.code))
     response.addErrback(lambda failure: log.err(failure))
     yield response
Ejemplo n.º 31
0
 def _call_rapidsms(self, message):
     config = yield self.get_config(message)
     http_method = config.rapidsms_http_method.encode("utf-8")
     headers = self.get_auth_headers(config)
     yield self._store_message(message, config.vumi_reply_timeout)
     response = http_request_full(config.rapidsms_url.geturl(),
                                  message.to_json(), headers, http_method)
     response.addCallback(lambda response: log.info(response.code))
     response.addErrback(lambda failure: log.err(failure))
     yield response
Ejemplo n.º 32
0
    def handle_outbound_message(self, message):

        # Errors
        message_id = message['message_id']
        if not message['content']:
            yield self.publish_nack(message_id, self.NO_CONTENT_ERROR)
            return
        if not message['in_reply_to']:
            yield self.publish_nack(message_id, self.NOT_REPLY_ERROR)
            return

        # Generate outbound message
        body = self.generate_body(
            message['to_addr'],  # msisdn
            message['transport_metadata']['sessionid'],  # sessionid
            message['transport_metadata']['appid'],  # appid
            message['in_reply_to'],
            message['content'],
            message['session_event']
        )
        log.info('BlastSMSUssdTransport outbound message with content: %r'
                 % (body,))

        # Finish Request
        response_id = self.finish_request(
            message['in_reply_to'],
            body,
        )

        # Response failure
        if response_id is None:
            # we don't yield on this publish because if a message store is
            # used, that causes the worker to wait for Riak before processing
            # the message and responding to USSD messages is time critical.
            self.publish_nack(message_id, self.RESPONSE_FAILURE_ERROR)
            return

        # we don't yield on this publish because if a message store is
        # used, that causes the worker to wait for Riak before processing
        # the message and responding to USSD messages is time critical.
        self.publish_ack(
            user_message_id=message_id, sent_message_id=message_id)
Ejemplo n.º 33
0
    def handle_event(self, event, handler_config):
        """

        Expects the event 'content' to be a dict with the following keys
        and values:

        NOTE:   perhaps all we need here is the message_id and lookup the
                rest of the required data from the message in the message store

        :param from_addr:
            the from_addr of the user interaction that triggered the event
        :param message_id:
            the id of the message that triggered the event, used to keep an
            audit trail of opt-outs and which messages they were triggered by.
        :param transport_type:
            the transport_type that the message was received on.

        """
        account_key = event.payload['account_key']
        oo_store = OptOutStore(self.vumi_api.manager, account_key)

        event_data = event.payload['content']

        from_addr = event_data['from_addr']
        message_id = event_data['message_id']
        transport_type = event_data.get('transport_type')

        if transport_type != 'ussd':
            log.info("SNAUSSDOptOutHandler skipping non-ussd"
                     " message for %r" % (from_addr,))
            return
        contact = yield self.find_contact(account_key, from_addr)
        if contact:
            opted_out = contact.extra['opted_out']
            if opted_out is not None and opted_out.isdigit():
                if int(opted_out) > 1:
                    yield oo_store.new_opt_out('msisdn', from_addr, {
                        'message_id': message_id,
                    })
                else:
                    yield oo_store.delete_opt_out('msisdn', from_addr)
Ejemplo n.º 34
0
 def populate_conversation_buckets(self):
     account_keys = yield self.find_account_keys()
     num_conversations = 0
     # We deliberarely serialise this. We don't want to hit the datastore
     # too hard for metrics.
     for account_key in account_keys:
         conv_keys = yield self.find_conversations_for_account(account_key)
         num_conversations += len(conv_keys)
         for conv_key in conv_keys:
             bucket = self.bucket_for_conversation(conv_key)
             if conv_key not in self._conversation_workers:
                 # TODO: Clear out archived conversations
                 user_api = self.vumi_api.get_user_api(account_key)
                 conv = yield user_api.get_wrapped_conversation(conv_key)
                 self._conversation_workers[conv_key] = conv.worker_name
             worker_name = self._conversation_workers[conv_key]
             self._buckets[bucket].append(
                 (account_key, conv_key, worker_name))
     log.info(
         "Scheduled metrics commands for %d conversations in %d accounts." %
         (num_conversations, len(account_keys)))
Ejemplo n.º 35
0
 def populate_conversation_buckets(self):
     account_keys = yield self.find_account_keys()
     num_conversations = 0
     # We deliberarely serialise this. We don't want to hit the datastore
     # too hard for metrics.
     for account_key in account_keys:
         conv_keys = yield self.find_conversations_for_account(account_key)
         num_conversations += len(conv_keys)
         for conv_key in conv_keys:
             bucket = self.bucket_for_conversation(conv_key)
             if conv_key not in self._conversation_workers:
                 # TODO: Clear out archived conversations
                 user_api = self.vumi_api.get_user_api(account_key)
                 conv = yield user_api.get_wrapped_conversation(conv_key)
                 self._conversation_workers[conv_key] = conv.worker_name
             worker_name = self._conversation_workers[conv_key]
             self._buckets[bucket].append(
                 (account_key, conv_key, worker_name))
     log.info(
         "Scheduled metrics commands for %d conversations in %d accounts."
         % (num_conversations, len(account_keys)))
Ejemplo n.º 36
0
    def handle_event(self, event, handler_config):
        """

        Hit the Yo payment gateway when a vxpoll is completed.

        Expects 'content' to be a dict with the following keys and values:

        :param from_addr:
            The address from which the message was received and which
            should be topped up with airtime.

        """

        participant = event['content']['participant']
        interactions = int(participant['interactions'])
        amount = interactions * int(self.amount)
        request_params = {
            'msisdn': event['content']['from_addr'],
            'amount': amount,
            'reason': self.reason,
        }

        headers = self.get_auth_headers(self.username, self.password)
        headers.update({
            'Content-Type': 'application/x-www-form-urlencoded',
        })

        log.info('Sending %s to %s via HTTP %s' % (
            request_params,
            self.url,
            self.method,
        ))
        response = yield http_request_full(self.url,
                                           data=urlencode(request_params),
                                           headers=headers,
                                           method=self.method)
        log.info('Received response: %s / %s' % (
            response.code,
            response.delivered_body,
        ))
Ejemplo n.º 37
0
 def get_conversation_fallback(self, account_key_or_batch_id, conv_key):
     # HACK: If we can't find a conversation, we might have an old entry
     # that uses a batch_id.
     conv = yield self.get_conversation(account_key_or_batch_id, conv_key)
     if conv is None:
         log.info("Trying to find conversation '%s' by batch_id." % (
                 conv_key,))
         batch = yield self.vumi_api.mdb.get_batch(account_key_or_batch_id)
         if batch is None:
             log.warning('Cannot find batch for batch_id %s' % (
                     account_key_or_batch_id,))
             return
         user_account_key = batch.metadata["user_account"]
         if user_account_key is None:
             log.warning("No account key in batch metadata: %r" % (batch,))
             return
         yield self.redis.srem('scheduled_conversations', json.dumps(
                 [account_key_or_batch_id, conv_key]))
         yield self.redis.sadd('scheduled_conversations', json.dumps(
                 [user_account_key, conv_key]))
         conv = yield self.get_conversation(user_account_key, conv_key)
     returnValue(conv)
Ejemplo n.º 38
0
    def handle_event(self, event, handler_config):
        """

        Hit the Yo payment gateway when a vxpoll is completed.

        Expects 'content' to be a dict with the following keys and values:

        :param from_addr:
            The address from which the message was received and which
            should be topped up with airtime.

        """

        participant = event['content']['participant']
        interactions = int(participant['interactions'])
        amount = interactions * int(self.amount)
        request_params = {
            'msisdn': event['content']['from_addr'],
            'amount': amount,
            'reason': self.reason,
        }

        headers = self.get_auth_headers(self.username, self.password)
        headers.update({
            'Content-Type': 'application/x-www-form-urlencoded',
            })

        log.info('Sending %s to %s via HTTP %s' % (
            request_params,
            self.url,
            self.method,
            ))
        response = yield http_request_full(self.url,
            data=urlencode(request_params),
            headers=headers,
            method=self.method)
        log.info('Received response: %s / %s' % (response.code,
            response.delivered_body,))
Ejemplo n.º 39
0
 def get_conversation_fallback(self, account_key_or_batch_id, conv_key):
     # HACK: If we can't find a conversation, we might have an old entry
     # that uses a batch_id.
     conv = yield self.get_conversation(account_key_or_batch_id, conv_key)
     if conv is None:
         log.info("Trying to find conversation '%s' by batch_id." %
                  (conv_key, ))
         batch = yield self.vumi_api.mdb.get_batch(account_key_or_batch_id)
         if batch is None:
             log.warning('Cannot find batch for batch_id %s' %
                         (account_key_or_batch_id, ))
             return
         user_account_key = batch.metadata["user_account"]
         if user_account_key is None:
             log.warning("No account key in batch metadata: %r" % (batch, ))
             return
         yield self.redis.srem(
             'scheduled_conversations',
             json.dumps([account_key_or_batch_id, conv_key]))
         yield self.redis.sadd('scheduled_conversations',
                               json.dumps([user_account_key, conv_key]))
         conv = yield self.get_conversation(user_account_key, conv_key)
     returnValue(conv)
Ejemplo n.º 40
0
    def handle_event(self, event, handler_config):
        """Set the appropriate subscription flag on a contact object.

        The event should have the following fields:

         * ``campaign_name``: Name of the campaign to manage subscription to.

         * ``operation``: Must be ``subscribe`` or ``unsubscribe``.

         * ``contact_id``: Contact id to operate on.

        An Example of a event_dispatcher.yaml config file, with mapped
        conversations in the config:

            transport_name: event_dispatcher
            event_handlers:
                subscription_handler:
                    go.vumitools.subscription.handlers.SubscriptionHandler
            account_handler_configs:
                '73ad76ec8c2e40858dc9d6b934049d95':
                - - ['a6a20571e77f4aa89a8b10a771b005bc', subscribe]
                  - - [subscription_handler, {}]
        """

        log.info(
            "SubscriptionHandler handling event: %s with config: %s" % (
            event, handler_config))
        user_api = self.get_user_api(event.payload['account_key'])
        fields = event.payload['content']

        contact = yield user_api.contact_store.get_contact_by_key(
            fields['contact_id'])
        contact.subscription[fields['campaign_name']] = {
            'subscribe': u'subscribed',
            'unsubscribe': u'unsubscribed',
            }[fields['operation']]
        yield contact.save()
Ejemplo n.º 41
0
    def handle_event(self, event, handler_config):
        """Set the appropriate subscription flag on a contact object.

        The event should have the following fields:

         * ``campaign_name``: Name of the campaign to manage subscription to.

         * ``operation``: Must be ``subscribe`` or ``unsubscribe``.

         * ``contact_id``: Contact id to operate on.

        An Example of a event_dispatcher.yaml config file, with mapped
        conversations in the config:

            transport_name: event_dispatcher
            event_handlers:
                subscription_handler:
                    go.vumitools.subscription.handlers.SubscriptionHandler
            account_handler_configs:
                '73ad76ec8c2e40858dc9d6b934049d95':
                - - ['a6a20571e77f4aa89a8b10a771b005bc', subscribe]
                  - - [subscription_handler, {}]
        """

        log.info("SubscriptionHandler handling event: %s with config: %s" %
                 (event, handler_config))
        user_api = self.get_user_api(event.payload['account_key'])
        fields = event.payload['content']

        contact = yield user_api.contact_store.get_contact_by_key(
            fields['contact_id'])
        contact.subscription[fields['campaign_name']] = {
            'subscribe': u'subscribed',
            'unsubscribe': u'unsubscribed',
        }[fields['operation']]
        yield contact.save()
Ejemplo n.º 42
0
 def consume_ack(self, event):
     log.info("Acknowledgement received for message %r"
              % (event['user_message_id']))
Ejemplo n.º 43
0
 def on_window_cleanup(self, window_id):
     log.info('Finished window %s, removing.' % (window_id,))
Ejemplo n.º 44
0
 def teardown_transport(self):
     config = self.get_static_config()
     log.info('Stopping ParlayX transport: %s' % (self.transport_name,))
     yield self.web_resource.loseConnection()
     if config.start_notifications:
         yield self._parlayx_client.stop_sms_notification()
Ejemplo n.º 45
0
    def handle_send_to_endpoint(self, api, command):
        """
        Sends a message to a specified endpoint.

        Command fields:
            - ``content``: The body of the reply message.
            - ``to_addr``: The address of the recipient (e.g. an MSISDN).
            - ``endpoint``: The name of the endpoint to send the message via.

        Reply fields:
            - ``success``: ``true`` if the operation was successful, otherwise
              ``false``.

        Example:

        .. code-block:: javascript

            api.request(
                'outbound.send_to_endpoint',
                {content: 'Welcome!', to_addr: '+27831234567',
                 endpoint: 'sms'},
                function(reply) { api.log_info('Message sent: ' +
                                               reply.success); });
        """
        if not 'content' in command:
            return self._mkfaild(command,
                                 reason=u"'content' must be given in sends.")
        if not isinstance(command['content'], (unicode, type(None))):
            return self._mkfaild(command,
                                 reason=u"'content' must be unicode or null.")
        if not isinstance(command.get('endpoint'), unicode):
            return self._mkfaild(command,
                                 reason=u"'endpoint' must be given in sends.")
        if not isinstance(command.get('to_addr'), unicode):
            return self._mkfaild(command,
                                 reason=u"'to_addr' must be given in sends.")

        endpoint = command['endpoint']
        content = command['content']
        to_addr = command['to_addr']

        conv = self.app_worker.conversation_for_api(api)
        if endpoint not in conv.extra_endpoints:
            return self._mkfaild(command,
                                 reason="Endpoint %r not configured" %
                                 (endpoint, ))

        msg_options = {}
        self.app_worker.add_conv_to_msg_options(conv, msg_options)

        log.info(
            "Sending outbound message to %r via endpoint %r, content: %r" %
            (to_addr, endpoint, content))

        d = self.app_worker.send_to(to_addr,
                                    content,
                                    endpoint=endpoint,
                                    **msg_options)

        d.addCallback(lambda r: self.reply(command, success=True))
        d.addErrback(
            lambda f: self._mkfail(command, unicode(f.getErrorMessage())))
        return d
Ejemplo n.º 46
0
 def consume_delivery_report(self, event):
     log.info("Delivery report received for message %r, status %r"
              % (event['user_message_id'], event['delivery_status']))
Ejemplo n.º 47
0
 def on_window_cleanup(self, window_id):
     log.info('Finished window %s, removing.' % (window_id, ))
Ejemplo n.º 48
0
 def test_system_filtering(self):
     lc = LogCatcher(system="^ab")
     with lc:
         log.info("Test 1", system="abc")
         log.info("Test 2", system="def")
     self.assertEqual(lc.messages(), ["Test 1"])
Ejemplo n.º 49
0
 def test_message_filtering(self):
     lc = LogCatcher(message="^Keep")
     with lc:
         log.info("Keep this")
         log.info("Discard this")
     self.assertEqual(lc.messages(), ["Keep this"])
Ejemplo n.º 50
0
 def send_event_to_client(self, event, conversation, push_url):
     if push_url is None:
         log.info("push_event_url not configured for conversation: %s" %
                  (conversation.key))
         return
     return self.push(push_url, event)
Ejemplo n.º 51
0
 def test_message_concatenation(self):
     lc = LogCatcher()
     with lc:
         log.info("Part 1", "Part 2")
     self.assertEqual(lc.messages(), ["Part 1 Part 2"])