def __init__(self, transport, config): super(DeliverShortMessageProcessor, self).__init__(transport, config) self.transport = transport self.redis = transport.redis self.config = self.CONFIG_CLASS(config, static=True) self.session_manager = SessionManager( self.redis, max_session_length=self.config.max_session_length)
def __init__(self, transport, config): super(SubmitShortMessageProcessor, self).__init__(transport, config) self.transport = transport self.redis = transport.redis self.config = self.CONFIG_CLASS(config, static=True) self.session_manager = SessionManager( self.redis, max_session_length=self.config.max_session_length)
def setup_transport(self): yield super(DmarkUssdTransport, self).setup_transport() config = self.get_static_config() r_prefix = "vumi.transports.dmark_ussd:%s" % self.transport_name self.session_manager = yield SessionManager.from_redis_config( config.redis_manager, r_prefix, max_session_length=config.ussd_session_timeout)
def setup_transport(self): super(TrueAfricanUssdTransport, self).setup_transport() config = self.get_static_config() # Session handling key_prefix = "trueafrican:%s" % self.transport_name self.session_manager = yield SessionManager.from_redis_config( config.redis_manager, key_prefix, config.session_timeout) # XMLRPC Resource self.web_resource = reactor.listenTCP(config.port, server.Site( XmlRpcResource(self)), interface=config.interface) # request tracking self.clock = self.get_clock() self._requests = {} self.request_timeout = config.request_timeout self.timeout_task = LoopingCall(self.request_timeout_cb) self.timeout_task.clock = self.clock self.timeout_task_d = self.timeout_task.start( self.TIMEOUT_TASK_INTERVAL, now=False) self.timeout_task_d.addErrback(log.err, "Request timeout handler failed")
def setup_transport(self): super(TrueAfricanUssdTransport, self).setup_transport() config = self.get_static_config() # Session handling key_prefix = "trueafrican:%s" % self.transport_name self.session_manager = yield SessionManager.from_redis_config( config.redis_manager, key_prefix, config.session_timeout ) # XMLRPC Resource self.web_resource = reactor.listenTCP( config.port, server.Site(XmlRpcResource(self)), interface=config.interface ) # request tracking self.clock = self.get_clock() self._requests = {} self.request_timeout = config.request_timeout self.timeout_task = LoopingCall(self.request_timeout_cb) self.timeout_task.clock = self.clock self.timeout_task_d = self.timeout_task.start( self.TIMEOUT_TASK_INTERVAL, now=False ) self.timeout_task_d.addErrback( log.err, "Request timeout handler failed" )
def setup_transport(self): super(MtechUssdTransport, self).setup_transport() r_config = self.config.get('redis_manager', {}) r_prefix = "mtech_ussd:%s" % self.transport_name session_timeout = int(self.config.get("ussd_session_timeout", 600)) self.session_manager = yield SessionManager.from_redis_config( r_config, r_prefix, max_session_length=session_timeout)
def setup_transport(self): super(CellulantTransport, self).setup_transport() r_config = self.config.get('redis_manager', {}) r_prefix = "vumi.transports.cellulant:%s" % self.transport_name session_timeout = int(self.config.get("ussd_session_timeout", 600)) self.session_manager = yield SessionManager.from_redis_config( r_config, r_prefix, session_timeout)
def setup_transport(self): log.msg('Starting the OperaInboundTransport config: %s' % self.transport_name) r_prefix = "%(transport_name)s@%(url)s" % self.config self.session_manager = yield SessionManager.from_redis_config( self.r_config, r_prefix, self.message_id_lifetime) self.proxy = xmlrpc.Proxy(self.opera_url) self.default_values = { 'Service': self.opera_service, 'Password': self.opera_password, 'Channel': self.opera_channel, } # start receipt web resource self.web_resource = yield self.start_web_resources( [ (OperaReceiptResource(self.handle_raw_incoming_receipt), self.web_receipt_path), (OperaReceiveResource(self.publish_message), self.web_receive_path), (OperaHealthResource(), 'health'), ], self.web_port )
def setup_transport(self): super(AirtelUSSDTransport, self).setup_transport() config = self.get_static_config() self.session_manager = yield SessionManager.from_redis_config( config.redis_manager, self.get_session_key_prefix(), config.ussd_session_timeout) if config.to_addr_pattern is not None: self.to_addr_re = re.compile(config.to_addr_pattern)
def setup_application(self): """Start the worker""" config = self.get_static_config() # Connect to Redis r_prefix = "hangman_game:%s:%s" % (config.transport_name, config.worker_name) self.session_manager = yield SessionManager.from_redis_config( config.redis_manager, r_prefix)
def setup_application(self): """Start the worker""" config = self.get_static_config() # Connect to Redis r_prefix = "hangman_game:%s:%s" % ( config.transport_name, config.worker_name) self.session_manager = yield SessionManager.from_redis_config( config.redis_manager, r_prefix)
def setup_transport(self): super(ImiMobileUssdTransport, self).setup_transport() # configure session manager r_config = self.config.get('redis_manager', {}) r_prefix = "vumi.transports.imimobile_ussd:%s" % self.transport_name session_timeout = int(self.config.get("ussd_session_timeout", 600)) self.session_manager = yield SessionManager.from_redis_config( r_config, r_prefix, max_session_length=session_timeout)
def setup_transport(self): config = self.get_static_config() self.client_factory = Factory.forProtocol(self.protocol_class) self.client_factory.vumi_transport = self prefix = "%s:ussd_codes" % (config.transport_name,) self.session_manager = yield SessionManager.from_redis_config( config.redis_manager, prefix, config.ussd_session_lifetime) self.client_service = self.get_service( config.twisted_endpoint, self.client_factory)
def setup_application(self): """Application specific setup""" self.app_config = self.get_static_config() self.server = TwilioAPIServer(self, self.app_config.api_version) path = os.path.join( self.app_config.web_path, self.app_config.api_version) self.webserver = self.start_web_resources([ (self.server.app.resource(), path)], self.app_config.web_port) redis = yield TxRedisManager.from_config(self.app_config.redis_manager) self.session_manager = SessionManager( redis, self.app_config.redis_timeout) self.session_lookup = SessionIDLookup( redis, self.app_config.redis_timeout, self.app_config.session_lookup_namespace)
def setup_transport(self): config = self.get_static_config() self.user_termination_response = config.user_termination_response r_prefix = "vumi.transports.mtn_nigeria:%s" % self.transport_name self.session_manager = yield SessionManager.from_redis_config( config.redis_manager, r_prefix, config.session_timeout_period) self.factory = MtnNigeriaUssdClientFactory( vumi_transport=self, username=config.username, password=config.password, application_id=config.application_id, enquire_link_interval=config.enquire_link_interval, timeout_period=config.timeout_period) self.client_connector = reactor.connectTCP(config.server_hostname, config.server_port, self.factory) log.msg('Connecting')
def setup_transport(self): config = self.get_static_config() self.user_termination_response = config.user_termination_response r_prefix = "vumi.transports.mtn_nigeria:%s" % self.transport_name self.session_manager = yield SessionManager.from_redis_config( config.redis_manager, r_prefix, config.session_timeout_period) self.factory = MtnNigeriaUssdClientFactory( vumi_transport=self, username=config.username, password=config.password, application_id=config.application_id, enquire_link_interval=config.enquire_link_interval, timeout_period=config.timeout_period) self.client_connector = reactor.connectTCP( config.server_hostname, config.server_port, self.factory) log.msg('Connecting')
def setup_transport(self): log.msg('Starting the OperaInboundTransport config: %s' % self.transport_name) r_prefix = "%(transport_name)s@%(url)s" % self.config self.session_manager = yield SessionManager.from_redis_config( self.r_config, r_prefix, self.message_id_lifetime) self.proxy = xmlrpc.Proxy(self.opera_url) self.default_values = { 'Service': self.opera_service, 'Password': self.opera_password, 'Channel': self.opera_channel, } # start receipt web resource self.web_resource = yield self.start_web_resources([ (OperaReceiptResource( self.handle_raw_incoming_receipt), self.web_receipt_path), (OperaReceiveResource( self.publish_message), self.web_receive_path), (OperaHealthResource(), 'health'), ], self.web_port)
def setup_transport(self): """ Transport specific setup - it sets up a connection. """ self._requests = {} self._requests_deferreds = {} self.callLater = reactor.callLater config = self.get_static_config() self.endpoint = config.twisted_endpoint self.timeout = config.timeout r_prefix = "vumi.transports.mtn_rwanda:%s" % self.transport_name self.session_manager = yield SessionManager.from_redis_config( config.redis_manager, r_prefix, config.session_timeout_period) self.factory = build_web_site({ config.health_path: HttpRpcHealthResource(self), config.web_path: MTNRwandaXMLRPCResource(self), }) self.xmlrpc_server = yield self.endpoint.listen(self.factory)
class DeliverShortMessageProcessor(default.DeliverShortMessageProcessor): CONFIG_CLASS = DeliverShortMessageProcessorConfig # NOTE: these keys are hexidecimal because of python-smpp encoding # quirkiness ussd_service_op_map = { '01': 'new', '12': 'continue', '81': 'close', # user abort } def __init__(self, transport, config): super(DeliverShortMessageProcessor, self).__init__(transport, config) self.transport = transport self.log = transport.log self.redis = transport.redis self.config = self.CONFIG_CLASS(config, static=True) self.session_manager = SessionManager( self.redis, max_session_length=self.config.max_session_length) @inlineCallbacks def handle_deliver_sm_ussd(self, pdu, pdu_params, pdu_opts): service_op = pdu_opts['ussd_service_op'] mica_session_identifier = pdu_opts['user_message_reference'] vumi_session_identifier = make_vumi_session_identifier( pdu_params['source_addr'], mica_session_identifier) session_event = self.ussd_service_op_map.get(service_op) if session_event == 'new': # PSSR request. Let's assume it means a new session. ussd_code = pdu_params['short_message'] content = None yield self.session_manager.create_session( vumi_session_identifier, ussd_code=ussd_code) elif session_event == 'close': session = yield self.session_manager.load_session( vumi_session_identifier) ussd_code = session['ussd_code'] content = None yield self.session_manager.clear_session(vumi_session_identifier) else: if session_event != 'continue': self.log.warning(( 'Received unknown %r ussd_service_op, assuming continue.') % (service_op,)) session_event = 'continue' session = yield self.session_manager.load_session( vumi_session_identifier) ussd_code = session['ussd_code'] content = self.dcs_decode( pdu_params['short_message'], pdu_params['data_coding']) # This is stashed on the message and available when replying # with a `submit_sm` session_info = { 'ussd_service_op': service_op, 'session_identifier': mica_session_identifier, } result = yield self.handle_short_message_content( source_addr=pdu_params['source_addr'], destination_addr=ussd_code, short_message=content, message_type='ussd', session_event=session_event, session_info=session_info) returnValue(result)
class SubmitShortMessageProcessor(default.SubmitShortMessageProcessor): CONFIG_CLASS = SubmitShortMessageProcessorConfig # NOTE: these values are hexidecimal because of python-smpp encoding # quirkiness ussd_service_op_map = { 'continue': '02', 'close': '17', # end } def __init__(self, transport, config): super(SubmitShortMessageProcessor, self).__init__(transport, config) self.transport = transport self.redis = transport.redis self.config = self.CONFIG_CLASS(config, static=True) self.session_manager = SessionManager( self.redis, max_session_length=self.config.max_session_length) @inlineCallbacks def handle_outbound_message(self, message, service): to_addr = message['to_addr'] from_addr = message['from_addr'] text = message['content'] if text is None: text = u"" vumi_message_id = message['message_id'] session_event = message['session_event'] transport_type = message['transport_type'] optional_parameters = {} if transport_type == 'ussd': continue_session = ( session_event != TransportUserMessage.SESSION_CLOSE) session_info = message['transport_metadata'].get( 'session_info', {}) mica_session_identifier = session_info.get( 'session_identifier', '') vumi_session_identifier = make_vumi_session_identifier( to_addr, mica_session_identifier) service_op = self.ussd_service_op_map[('continue' if continue_session else 'close')] optional_parameters.update({ 'ussd_service_op': service_op, 'user_message_reference': ( str(mica_session_identifier).zfill(2)), }) if not continue_session: yield self.session_manager.clear_session( vumi_session_identifier) resp = yield self.send_short_message( service, vumi_message_id, to_addr.encode('ascii'), text.encode(self.config.submit_sm_encoding), data_coding=self.config.submit_sm_data_coding, source_addr=from_addr.encode('ascii'), optional_parameters=optional_parameters) returnValue(resp)
class ContentKeywordRouter(SimpleDispatchRouter): """Router that dispatches based on the first word of the message content. In the context of SMSes the first word is sometimes called the 'keyword'. :param dict keyword_mappings: Mapping from application transport names to simple keywords. This is purely a convenience for constructing simple routing rules. The rules generated from this option are appened to the of rules supplied via the *rules* option. :param list rules: A list of routing rules. A routing rule is a dictionary. It must have `app` and `keyword` keys and may contain `to_addr` and `prefix` keys. If a message's first word matches a given keyword, the message is sent to the application listening on the transport name given by the value of `app`. If a 'to_addr' key is supplied, the message `to_addr` must also match the value of the 'to_addr' key. If a 'prefix' is supplied, the message `from_addr` must *start with* the value of the 'prefix' key. :param str fallback_application: Optional application transport name to forward inbound messages that match no rule to. If omitted, unrouted inbound messages are just logged. :param dict transport_mappings: Mapping from message `from_addr` values to transports names. If a message's from_addr matches a given from_addr, the message is sent to the associated transport. :param int expire_routing_memory: Time in seconds before outbound message's ids are expired from the redis routing store. Outbound message ids are stored along with the transport_name the message came in on and are used to route events such as acknowledgements and delivery reports back to the application that sent the outgoing message. Default is seven days. """ DEFAULT_ROUTING_TIMEOUT = 60 * 60 * 24 * 7 # 7 days def setup_routing(self): self.r_config = self.config.get('redis_manager', {}) self.r_prefix = self.config['dispatcher_name'] self.rules = [] for rule in self.config.get('rules', []): if 'keyword' not in rule or 'app' not in rule: raise ConfigError("Rule definition %r must contain values for" " both 'app' and 'keyword'" % rule) rule = rule.copy() rule['keyword'] = rule['keyword'].lower() self.rules.append(rule) keyword_mappings = self.config.get('keyword_mappings', {}) for transport_name, keyword in keyword_mappings.items(): self.rules.append({'app': transport_name, 'keyword': keyword.lower()}) self.fallback_application = self.config.get('fallback_application') self.transport_mappings = self.config['transport_mappings'] self.expire_routing_timeout = int(self.config.get( 'expire_routing_memory', self.DEFAULT_ROUTING_TIMEOUT)) # FIXME: The following is a hack to deal with sync-only setup. self._redis_d = TxRedisManager.from_config(self.r_config) self._redis_d.addCallback(lambda m: m.sub_manager(self.r_prefix)) self._redis_d.addCallback(self._setup_redis) def _setup_redis(self, redis): self.redis = redis self.session_manager = SessionManager( self.redis, self.expire_routing_timeout) def get_message_key(self, message): return 'message:%s' % (message,) def publish_transport(self, name, msg): self.dispatcher.publish_outbound_message(name, msg) def publish_exposed_inbound(self, name, msg): self.dispatcher.publish_inbound_message(name, msg) def publish_exposed_event(self, name, msg): self.dispatcher.publish_inbound_event(name, msg) def is_msg_matching_routing_rules(self, keyword, msg, rule): return all([keyword == rule['keyword'], (not 'to_addr' in rule) or (msg['to_addr'] == rule['to_addr']), (not 'prefix' in rule) or (msg['from_addr'].startswith(rule['prefix']))]) def dispatch_inbound_message(self, msg): keyword = get_first_word(msg['content']).lower() matched = False for rule in self.rules: if self.is_msg_matching_routing_rules(keyword, msg, rule): matched = True # copy message so that the middleware doesn't see a particular # message instance multiple times self.publish_exposed_inbound(rule['app'], msg.copy()) if not matched: if self.fallback_application is not None: self.publish_exposed_inbound(self.fallback_application, msg) else: log.error(DispatcherError( 'Message could not be routed: %r' % (msg,))) @inlineCallbacks def dispatch_inbound_event(self, msg): yield self._redis_d # Horrible hack to ensure we have it setup. message_key = self.get_message_key(msg['user_message_id']) session = yield self.session_manager.load_session(message_key) name = session.get('name') if not name: log.error(DispatcherError( "No transport_name for return route found in Redis" " while dispatching transport event for message %s" % (msg['user_message_id'],))) try: self.publish_exposed_event(name, msg) except: log.error(DispatcherError("No publishing route for %s" % (name,))) @inlineCallbacks def dispatch_outbound_message(self, msg): yield self._redis_d # Horrible hack to ensure we have it setup. transport_name = self.transport_mappings.get(msg['from_addr']) if transport_name is not None: self.publish_transport(transport_name, msg) message_key = self.get_message_key(msg['message_id']) yield self.session_manager.create_session( message_key, name=msg['transport_name']) else: log.error(DispatcherError( "No transport for %s" % (msg['from_addr'],)))
def _setup_redis(self, redis): self.redis = redis self.session_manager = SessionManager( self.redis, self.expire_routing_timeout)
def setup_transport(self): super(SafaricomTransport, self).setup_transport() self.session_manager = yield SessionManager.from_redis_config( self.redis_config, self.r_prefix, self.r_session_timeout)
class TestSessionManager(VumiTestCase): @inlineCallbacks def setUp(self): self.persistence_helper = self.add_helper(PersistenceHelper()) self.manager = yield self.persistence_helper.get_redis_manager() yield self.manager._purge_all() # Just in case self.sm = SessionManager(self.manager) self.add_cleanup(self.sm.stop) @inlineCallbacks def test_active_sessions(self): def get_sessions(): return self.sm.active_sessions().addCallback(lambda s: sorted(s)) def ids(): return get_sessions().addCallback(lambda s: [x[0] for x in s]) self.assertEqual((yield ids()), []) yield self.sm.create_session("u1") self.assertEqual((yield ids()), ["u1"]) # 10 seconds later yield self.sm.create_session("u2", created_at=time.time() + 10) self.assertEqual((yield ids()), ["u1", "u2"]) s1, s2 = yield get_sessions() self.assertTrue(s1[1]['created_at'] < s2[1]['created_at']) @inlineCallbacks def test_schedule_session_expiry(self): self.sm.max_session_length = 60.0 yield self.sm.create_session("u1") @inlineCallbacks def test_create_and_retrieve_session(self): session = yield self.sm.create_session("u1") self.assertEqual(sorted(session.keys()), ['created_at']) self.assertTrue(time.time() - float(session['created_at']) < 10.0) loaded = yield self.sm.load_session("u1") self.assertEqual(loaded, session) @inlineCallbacks def test_create_clears_existing_session(self): session = yield self.sm.create_session("u1", foo="bar") self.assertEqual(sorted(session.keys()), ['created_at', 'foo']) loaded = yield self.sm.load_session("u1") self.assertEqual(loaded, session) session = yield self.sm.create_session("u1", bar="baz") self.assertEqual(sorted(session.keys()), ['bar', 'created_at']) loaded = yield self.sm.load_session("u1") self.assertEqual(loaded, session) @inlineCallbacks def test_save_session(self): test_session = {"foo": 5, "bar": "baz"} yield self.sm.create_session("u1") yield self.sm.save_session("u1", test_session) session = yield self.sm.load_session("u1") self.assertTrue(session.pop('created_at') is not None) # Redis saves & returns all session values as strings self.assertEqual(session, dict([map(str, kvs) for kvs in test_session.items()]))
class DeliverShortMessageProcessor(default.DeliverShortMessageProcessor): CONFIG_CLASS = DeliverShortMessageProcessorConfig # NOTE: these keys are hexidecimal because of python-smpp encoding # quirkiness ussd_service_op_map = { '01': 'new', '12': 'continue', '81': 'close', # user abort } def __init__(self, transport, config): super(DeliverShortMessageProcessor, self).__init__(transport, config) self.transport = transport self.redis = transport.redis self.config = self.CONFIG_CLASS(config, static=True) self.session_manager = SessionManager( self.redis, max_session_length=self.config.max_session_length) @inlineCallbacks def handle_deliver_sm_ussd(self, pdu, pdu_params, pdu_opts): service_op = pdu_opts['ussd_service_op'] # 6D uses its_session_info as follows: # # * First 15 bit: dialog id (i.e. session id) # * Last bit: end session (1 to end, 0 to continue) its_session_number = int(pdu_opts['its_session_info'], 16) end_session = bool(its_session_number % 2) sixdee_session_identifier = "%04x" % (its_session_number & 0xfffe) vumi_session_identifier = make_vumi_session_identifier( pdu_params['source_addr'], sixdee_session_identifier) if end_session: session_event = 'close' else: session_event = self.ussd_service_op_map.get(service_op) if session_event == 'new': # PSSR request. Let's assume it means a new session. ussd_code = pdu_params['short_message'] content = None yield self.session_manager.create_session(vumi_session_identifier, ussd_code=ussd_code) elif session_event == 'close': session = yield self.session_manager.load_session( vumi_session_identifier) ussd_code = session['ussd_code'] content = None yield self.session_manager.clear_session(vumi_session_identifier) else: if session_event != 'continue': log.warning(('Received unknown %r ussd_service_op, ' 'assuming continue.') % (service_op, )) session_event = 'continue' session = yield self.session_manager.load_session( vumi_session_identifier) ussd_code = session['ussd_code'] content = pdu_params['short_message'] # This is stashed on the message and available when replying # with a `submit_sm` session_info = { 'ussd_service_op': service_op, 'session_identifier': sixdee_session_identifier, } decoded_msg = self.dcs_decode(content, pdu_params['data_coding']) result = yield self.handle_short_message_content( source_addr=pdu_params['source_addr'], destination_addr=ussd_code, short_message=decoded_msg, message_type='ussd', session_event=session_event, session_info=session_info) returnValue(result)
class DeliverShortMessageProcessor(default.DeliverShortMessageProcessor): CONFIG_CLASS = DeliverShortMessageProcessorConfig def __init__(self, transport, config): super(DeliverShortMessageProcessor, self).__init__(transport, config) self.transport = transport self.redis = transport.redis self.config = self.CONFIG_CLASS(config, static=True) self.session_manager = SessionManager( self.redis, max_session_length=self.config.max_session_length) @inlineCallbacks def handle_deliver_sm_ussd(self, pdu, pdu_params, pdu_opts): service_op = pdu_opts['ussd_service_op'] mica_session_identifier = pdu_opts['user_message_reference'] vumi_session_identifier = make_vumi_session_identifier( pdu_params['source_addr'], mica_session_identifier) session_event = 'close' if service_op == '01': # PSSR request. Let's assume it means a new session. session_event = 'new' ussd_code = pdu_params['short_message'] content = None yield self.session_manager.create_session( vumi_session_identifier, ussd_code=ussd_code) elif service_op == '17': # PSSR response. This means session end. session_event = 'close' session = yield self.session_manager.load_session( vumi_session_identifier) ussd_code = session['ussd_code'] content = None yield self.session_manager.clear_session(vumi_session_identifier) else: session_event = 'continue' session = yield self.session_manager.load_session( vumi_session_identifier) ussd_code = session['ussd_code'] content = pdu_params['short_message'] # This is stashed on the message and available when replying # with a `submit_sm` session_info = { 'session_identifier': mica_session_identifier, } decoded_msg = self.dcs_decode(content, pdu_params['data_coding']) result = yield self.handle_short_message_content( source_addr=pdu_params['source_addr'], destination_addr=ussd_code, short_message=decoded_msg, message_type='ussd', session_event=session_event, session_info=session_info) returnValue(result)
def session_manager(self, config): key_prefix = ':'.join((self.worker_name, config.router.key)) redis = self.redis.sub_manager(key_prefix) return SessionManager(redis, max_session_length=config.session_expiry)
class ContentKeywordRouter(SimpleDispatchRouter): """Router that dispatches based on the first word of the message content. In the context of SMSes the first word is sometimes called the 'keyword'. :param dict keyword_mappings: Mapping from application transport names to simple keywords. This is purely a convenience for constructing simple routing rules. The rules generated from this option are appened to the of rules supplied via the *rules* option. :param list rules: A list of routing rules. A routing rule is a dictionary. It must have `app` and `keyword` keys and may contain `to_addr` and `prefix` keys. If a message's first word matches a given keyword, the message is sent to the application listening on the transport name given by the value of `app`. If a 'to_addr' key is supplied, the message `to_addr` must also match the value of the 'to_addr' key. If a 'prefix' is supplied, the message `from_addr` must *start with* the value of the 'prefix' key. :param str fallback_application: Optional application transport name to forward inbound messages that match no rule to. If omitted, unrouted inbound messages are just logged. :param dict transport_mappings: Mapping from message `from_addr` values to transports names. If a message's from_addr matches a given from_addr, the message is sent to the associated transport. :param int expire_routing_memory: Time in seconds before outbound message's ids are expired from the redis routing store. Outbound message ids are stored along with the transport_name the message came in on and are used to route events such as acknowledgements and delivery reports back to the application that sent the outgoing message. Default is seven days. """ DEFAULT_ROUTING_TIMEOUT = 60 * 60 * 24 * 7 # 7 days def setup_routing(self): self.r_config = self.config.get('redis_manager', {}) self.r_prefix = self.config['dispatcher_name'] self.rules = [] for rule in self.config.get('rules', []): if 'keyword' not in rule or 'app' not in rule: raise ConfigError("Rule definition %r must contain values for" " both 'app' and 'keyword'" % rule) rule = rule.copy() rule['keyword'] = rule['keyword'].lower() self.rules.append(rule) keyword_mappings = self.config.get('keyword_mappings', {}) for transport_name, keyword in keyword_mappings.items(): self.rules.append({ 'app': transport_name, 'keyword': keyword.lower() }) self.fallback_application = self.config.get('fallback_application') self.transport_mappings = self.config['transport_mappings'] self.expire_routing_timeout = int( self.config.get('expire_routing_memory', self.DEFAULT_ROUTING_TIMEOUT)) # FIXME: The following is a hack to deal with sync-only setup. self._redis_d = TxRedisManager.from_config(self.r_config) self._redis_d.addCallback(lambda m: m.sub_manager(self.r_prefix)) self._redis_d.addCallback(self._setup_redis) def _setup_redis(self, redis): self.redis = redis self.session_manager = SessionManager(self.redis, self.expire_routing_timeout) def get_message_key(self, message): return 'message:%s' % (message, ) def publish_transport(self, name, msg): self.dispatcher.publish_outbound_message(name, msg) def publish_exposed_inbound(self, name, msg): self.dispatcher.publish_inbound_message(name, msg) def publish_exposed_event(self, name, msg): self.dispatcher.publish_inbound_event(name, msg) def is_msg_matching_routing_rules(self, keyword, msg, rule): return all([ keyword == rule['keyword'], (not 'to_addr' in rule) or (msg['to_addr'] == rule['to_addr']), (not 'prefix' in rule) or (msg['from_addr'].startswith(rule['prefix'])) ]) def dispatch_inbound_message(self, msg): keyword = get_first_word(msg['content']).lower() matched = False for rule in self.rules: if self.is_msg_matching_routing_rules(keyword, msg, rule): matched = True # copy message so that the middleware doesn't see a particular # message instance multiple times self.publish_exposed_inbound(rule['app'], msg.copy()) if not matched: if self.fallback_application is not None: self.publish_exposed_inbound(self.fallback_application, msg) else: log.error( DispatcherError('Message could not be routed: %r' % (msg, ))) @inlineCallbacks def dispatch_inbound_event(self, msg): yield self._redis_d # Horrible hack to ensure we have it setup. message_key = self.get_message_key(msg['user_message_id']) session = yield self.session_manager.load_session(message_key) name = session.get('name') if not name: log.error( DispatcherError( "No transport_name for return route found in Redis" " while dispatching transport event for message %s" % (msg['user_message_id'], ))) try: self.publish_exposed_event(name, msg) except: log.error(DispatcherError("No publishing route for %s" % (name, ))) @inlineCallbacks def dispatch_outbound_message(self, msg): yield self._redis_d # Horrible hack to ensure we have it setup. transport_name = self.transport_mappings.get(msg['from_addr']) if transport_name is not None: self.publish_transport(transport_name, msg) message_key = self.get_message_key(msg['message_id']) yield self.session_manager.create_session( message_key, name=msg['transport_name']) else: log.error( DispatcherError("No transport for %s" % (msg['from_addr'], )))
class TwilioAPIWorker(ApplicationWorker): """Emulates the Twilio API to use vumi as if it was Twilio""" CONFIG_CLASS = TwilioAPIConfig @inlineCallbacks def setup_application(self): """Application specific setup""" self.app_config = self.get_static_config() self.server = TwilioAPIServer(self, self.app_config.api_version) path = os.path.join( self.app_config.web_path, self.app_config.api_version) self.webserver = self.start_web_resources([ (self.server.app.resource(), path)], self.app_config.web_port) redis = yield TxRedisManager.from_config(self.app_config.redis_manager) self.session_manager = SessionManager( redis, self.app_config.redis_timeout) self.session_lookup = SessionIDLookup( redis, self.app_config.redis_timeout, self.app_config.session_lookup_namespace) @inlineCallbacks def teardown_application(self): """Clean-up of setup done in `setup_application`""" yield self.webserver.loseConnection() yield self.session_manager.stop() def _http_request(self, url='', method='GET', data={}): return treq.request(method, url, persistent=False, data=data) def _request_data_from_session(self, session): return { 'CallSid': session['CallId'], 'AccountSid': session['AccountSid'], 'From': session['From'], 'To': session['To'], 'CallStatus': session['Status'], 'ApiVersion': self.app_config.api_version, 'Direction': session['Direction'], } @inlineCallbacks def _get_twiml_from_client(self, session, data=None): if data is None: data = self._request_data_from_session(session) twiml_raw = yield self._http_request( session['Url'], session['Method'], data) if twiml_raw.code < 200 or twiml_raw.code >= 300: twiml_raw = yield self._http_request( session['FallbackUrl'], session['FallbackMethod'], data) twiml_raw = yield twiml_raw.content() twiml_parser = TwiMLParser(session['Url']) returnValue(twiml_parser.parse(twiml_raw)) @inlineCallbacks def _handle_connected_call( self, session_id, session, status='in-progress', twiml=None): # TODO: Support sending ForwardedFrom parameter # TODO: Support sending CallerName parameter # TODO: Support sending geographic data parameters session['Status'] = status self.session_manager.save_session(session_id, session) if twiml is None: twiml = yield self._get_twiml_from_client(session) for verb in twiml: if verb.name == "Play": # TODO: Support loop and digit attributes yield self._send_message(verb.nouns[0], session) elif verb.name == "Hangup": yield self._send_message( None, session, TransportUserMessage.SESSION_CLOSE) yield self.session_manager.clear_session(session_id) break elif verb.name == "Gather": # TODO: Support timeout and numDigits attributes msgs = [] for subverb in verb.nouns: # TODO: Support Say and Pause subverbs if subverb.name == "Play": msgs.append({'speech_url': subverb.nouns[0]}) session['Gather_Action'] = verb.attributes['action'] session['Gather_Method'] = verb.attributes['method'] yield self.session_manager.save_session(session_id, session) if len(msgs) == 0: msgs.append({'speech_url': None}) msgs[-1]['wait_for'] = verb.attributes['finishOnKey'] for msg in msgs: yield self._send_message( msg['speech_url'], session, wait_for=msg.get('wait_for')) break def _send_message(self, url, session, session_event=None, wait_for=None): helper_metadata = {'voice': {}} if url is not None: helper_metadata['voice']['speech_url'] = url if wait_for is not None: helper_metadata['voice']['wait_for'] = wait_for return self.send_to( session['To'], None, from_addr=session['From'], session_event=session_event, to_addr_type=TransportUserMessage.AT_MSISDN, from_addr_type=TransportUserMessage.AT_MSISDN, helper_metadata=helper_metadata) @inlineCallbacks def consume_user_message(self, message): # At the moment there is no way to determine whether or not a message # is the result of a wait_for or just a single digit, so if the Gather # data exists inside the current session data, then we assume that it # is the result of a Gather # TODO: Fix this session = yield self.session_manager.load_session(message['from_addr']) if session.get('Gather_Action') and session.get('Gather_Method'): data = self._request_data_from_session(session) data['Digits'] = message['content'] twiml = yield self._get_twiml_from_client({ 'Url': session['Gather_Action'], 'Method': session['Gather_Method'], 'Fallback_Url': None, 'Fallback_Method': None, }, data=data) yield self._handle_connected_call( message['from_addr'], session, twiml=twiml) @inlineCallbacks def consume_ack(self, event): message_id = event['user_message_id'] session_id = yield self.session_lookup.get_address(message_id) yield self.session_lookup.delete_id(message_id) session = yield self.session_manager.load_session(session_id) if session['Status'] == 'queued': yield self._handle_connected_call(session_id, session) @inlineCallbacks def consume_nack(self, event): message_id = event['user_message_id'] session_id = yield self.session_lookup.get_address(message_id) yield self.session_lookup.delete_id(message_id) session = yield self.session_manager.load_session(session_id) if session['Status'] == 'queued': yield self._handle_connected_call( session_id, session, status='failed') @inlineCallbacks def new_session(self, message): yield self.session_lookup.set_id( message['message_id'], message['from_addr']) config = yield self.get_config(message) session = { 'CallId': self.server._get_sid(), 'AccountSid': self.server._get_sid(), 'From': message['from_addr'], 'To': message['to_addr'], 'Status': 'in-progress', 'Direction': 'inbound', 'Url': config.client_path, 'Method': config.client_method, 'StatusCallback': config.status_callback_path, 'StatusCallbackMethod': config.status_callback_method, } yield self.session_manager.create_session( message['from_addr'], **session) twiml = yield self._get_twiml_from_client(session) for verb in twiml: if verb.name == "Play": yield self.reply_to(message, None, helper_metadata={ 'voice': { 'speech_url': verb.nouns[0], } }) elif verb.name == "Hangup": yield self.reply_to( message, None, session_event=TransportUserMessage.SESSION_CLOSE) yield self.session_manager.clear_session(message['from_addr']) break elif verb.name == "Gather": # TODO: Support timeout and numDigits attributes msgs = [] for subverb in verb.nouns: # TODO: Support Say and Pause subverbs if subverb.name == "Play": msgs.append({'speech_url': subverb.nouns[0]}) session['Gather_Action'] = verb.attributes['action'] session['Gather_Method'] = verb.attributes['method'] yield self.session_manager.save_session( message['from_addr'], session) if len(msgs) == 0: msgs.append({'speech_url': None}) msgs[-1]['wait_for'] = verb.attributes['finishOnKey'] for msg in msgs: yield self.reply_to(message, None, helper_metadata={ 'voice': { 'speech_url': msg.get('speech_url'), 'wait_for': msg.get('wait_for'), }}) break @inlineCallbacks def close_session(self, message): # TODO: Implement call duration parameters # TODO: Implement recording parameters session = yield self.session_manager.load_session(message['from_addr']) yield self.session_manager.clear_session(message['from_addr']) url = session.get('StatusCallback') if url and url != 'None': session['Status'] = 'completed' data = self._request_data_from_session(session) yield self._http_request( session['StatusCallback'], session['StatusCallbackMethod'], data)
def setUp(self): self.persistence_helper = self.add_helper(PersistenceHelper()) self.manager = yield self.persistence_helper.get_redis_manager() yield self.manager._purge_all() # Just in case self.sm = SessionManager(self.manager) self.add_cleanup(self.sm.stop)
def _setup_redis(self, redis): self.redis = redis self.session_manager = SessionManager(self.redis, self.expire_routing_timeout)
class DeliverShortMessageProcessor(default.DeliverShortMessageProcessor): CONFIG_CLASS = DeliverShortMessageProcessorConfig # NOTE: these keys are hexidecimal because of python-smpp encoding # quirkiness ussd_service_op_map = { '01': 'new', '12': 'continue', '81': 'close', # user abort } def __init__(self, transport, config): super(DeliverShortMessageProcessor, self).__init__(transport, config) self.transport = transport self.log = transport.log self.redis = transport.redis self.config = self.CONFIG_CLASS(config, static=True) self.session_manager = SessionManager( self.redis, max_session_length=self.config.max_session_length) @inlineCallbacks def handle_deliver_sm_ussd(self, pdu, pdu_params, pdu_opts): service_op = pdu_opts['ussd_service_op'] mica_session_identifier = pdu_opts['user_message_reference'] vumi_session_identifier = make_vumi_session_identifier( pdu_params['source_addr'], mica_session_identifier) session_event = self.ussd_service_op_map.get(service_op) if session_event == 'new': # PSSR request. Let's assume it means a new session. ussd_code = pdu_params['short_message'] content = None yield self.session_manager.create_session(vumi_session_identifier, ussd_code=ussd_code) elif session_event == 'close': session = yield self.session_manager.load_session( vumi_session_identifier) ussd_code = session['ussd_code'] content = None yield self.session_manager.clear_session(vumi_session_identifier) else: if session_event != 'continue': self.log.warning( ('Received unknown %r ussd_service_op, assuming continue.') % (service_op, )) session_event = 'continue' session = yield self.session_manager.load_session( vumi_session_identifier) ussd_code = session['ussd_code'] content = self.dcs_decode(pdu_params['short_message'], pdu_params['data_coding']) # This is stashed on the message and available when replying # with a `submit_sm` session_info = { 'ussd_service_op': service_op, 'session_identifier': mica_session_identifier, } result = yield self.handle_short_message_content( source_addr=pdu_params['source_addr'], destination_addr=ussd_code, short_message=content, message_type='ussd', session_event=session_event, session_info=session_info) returnValue(result)
class SubmitShortMessageProcessor(default.SubmitShortMessageProcessor): CONFIG_CLASS = SubmitShortMessageProcessorConfig # NOTE: these values are hexidecimal because of python-smpp encoding # quirkiness ussd_service_op_map = { 'continue': '02', 'close': '17', # end } def __init__(self, transport, config): super(SubmitShortMessageProcessor, self).__init__(transport, config) self.transport = transport self.redis = transport.redis self.config = self.CONFIG_CLASS(config, static=True) self.session_manager = SessionManager( self.redis, max_session_length=self.config.max_session_length) @inlineCallbacks def handle_outbound_message(self, message, protocol): to_addr = message['to_addr'] from_addr = message['from_addr'] text = message['content'] if text is None: text = u"" vumi_message_id = message['message_id'] session_event = message['session_event'] transport_type = message['transport_type'] optional_parameters = {} if transport_type == 'ussd': continue_session = (session_event != TransportUserMessage.SESSION_CLOSE) session_info = message['transport_metadata'].get( 'session_info', {}) sixdee_session_identifier = session_info.get( 'session_identifier', '') vumi_session_identifier = make_vumi_session_identifier( to_addr, sixdee_session_identifier) its_session_info = (int(sixdee_session_identifier, 16) | int(not continue_session)) service_op = self.ussd_service_op_map[( 'continue' if continue_session else 'close')] optional_parameters.update({ 'ussd_service_op': service_op, 'its_session_info': "%04x" % (its_session_info, ) }) if not continue_session: yield self.session_manager.clear_session( vumi_session_identifier) if self.config.send_long_messages: resp = yield protocol.submit_sm_long( vumi_message_id, to_addr.encode('ascii'), long_message=text.encode(self.config.submit_sm_encoding), data_coding=self.config.submit_sm_data_coding, source_addr=from_addr.encode('ascii'), optional_parameters=optional_parameters, ) elif self.config.send_multipart_sar: resp = yield protocol.submit_csm_sar( vumi_message_id, to_addr.encode('ascii'), short_message=text.encode(self.config.submit_sm_encoding), data_coding=self.config.submit_sm_data_coding, source_addr=from_addr.encode('ascii'), optional_parameters=optional_parameters, ) elif self.config.send_multipart_udh: resp = yield protocol.submit_csm_udh( vumi_message_id, to_addr.encode('ascii'), short_message=text.encode(self.config.submit_sm_encoding), data_coding=self.config.submit_sm_data_coding, source_addr=from_addr.encode('ascii'), optional_parameters=optional_parameters, ) else: resp = yield protocol.submit_sm( vumi_message_id, to_addr.encode('ascii'), short_message=text.encode(self.config.submit_sm_encoding), data_coding=self.config.submit_sm_data_coding, source_addr=from_addr.encode('ascii'), optional_parameters=optional_parameters, ) returnValue(resp)
class SubmitShortMessageProcessor(default.SubmitShortMessageProcessor): CONFIG_CLASS = SubmitShortMessageProcessorConfig # NOTE: these values are hexidecimal because of python-smpp encoding # quirkiness ussd_service_op_map = { 'continue': '02', 'close': '17', # end } def __init__(self, transport, config): super(SubmitShortMessageProcessor, self).__init__(transport, config) self.transport = transport self.redis = transport.redis self.config = self.CONFIG_CLASS(config, static=True) self.session_manager = SessionManager( self.redis, max_session_length=self.config.max_session_length) @inlineCallbacks def handle_outbound_message(self, message, protocol): to_addr = message['to_addr'] from_addr = message['from_addr'] text = message['content'] if text is None: text = u"" vumi_message_id = message['message_id'] session_event = message['session_event'] transport_type = message['transport_type'] optional_parameters = {} if transport_type == 'ussd': continue_session = ( session_event != TransportUserMessage.SESSION_CLOSE) session_info = message['transport_metadata'].get( 'session_info', {}) sixdee_session_identifier = session_info.get( 'session_identifier', '') vumi_session_identifier = make_vumi_session_identifier( to_addr, sixdee_session_identifier) its_session_info = ( int(sixdee_session_identifier, 16) | int(not continue_session)) service_op = self.ussd_service_op_map[('continue' if continue_session else 'close')] optional_parameters.update({ 'ussd_service_op': service_op, 'its_session_info': "%04x" % (its_session_info,) }) if not continue_session: yield self.session_manager.clear_session( vumi_session_identifier) if self.config.send_long_messages: resp = yield protocol.submit_sm_long( vumi_message_id, to_addr.encode('ascii'), long_message=text.encode(self.config.submit_sm_encoding), data_coding=self.config.submit_sm_data_coding, source_addr=from_addr.encode('ascii'), optional_parameters=optional_parameters, ) elif self.config.send_multipart_sar: resp = yield protocol.submit_csm_sar( vumi_message_id, to_addr.encode('ascii'), short_message=text.encode(self.config.submit_sm_encoding), data_coding=self.config.submit_sm_data_coding, source_addr=from_addr.encode('ascii'), optional_parameters=optional_parameters, ) elif self.config.send_multipart_udh: resp = yield protocol.submit_csm_udh( vumi_message_id, to_addr.encode('ascii'), short_message=text.encode(self.config.submit_sm_encoding), data_coding=self.config.submit_sm_data_coding, source_addr=from_addr.encode('ascii'), optional_parameters=optional_parameters, ) else: resp = yield protocol.submit_sm( vumi_message_id, to_addr.encode('ascii'), short_message=text.encode(self.config.submit_sm_encoding), data_coding=self.config.submit_sm_data_coding, source_addr=from_addr.encode('ascii'), optional_parameters=optional_parameters, ) returnValue(resp)
class SubmitShortMessageProcessor(default.SubmitShortMessageProcessor): CONFIG_CLASS = SubmitShortMessageProcessorConfig def __init__(self, transport, config): super(SubmitShortMessageProcessor, self).__init__(transport, config) self.transport = transport self.redis = transport.redis self.config = self.CONFIG_CLASS(config, static=True) self.session_manager = SessionManager( self.redis, max_session_length=self.config.max_session_length) @inlineCallbacks def handle_outbound_message(self, message, protocol): to_addr = message['to_addr'] from_addr = message['from_addr'] text = message['content'] vumi_message_id = message['message_id'] session_event = message['session_event'] transport_type = message['transport_type'] optional_parameters = {} if transport_type == 'ussd': continue_session = ( session_event != TransportUserMessage.SESSION_CLOSE) session_info = message['transport_metadata'].get( 'session_info', {}) mica_session_identifier = session_info.get( 'session_identifier', '') vumi_session_identifier = make_vumi_session_identifier( to_addr, mica_session_identifier) optional_parameters.update({ 'ussd_service_op': ('02' if continue_session else '17'), 'user_message_reference': ( str(mica_session_identifier).zfill(2)), }) if not continue_session: yield self.session_manager.clear_session( vumi_session_identifier) if self.config.send_long_messages: resp = yield protocol.submit_sm_long( vumi_message_id, to_addr.encode('ascii'), long_message=text.encode(self.config.submit_sm_encoding), data_coding=self.config.submit_sm_data_coding, source_addr=from_addr.encode('ascii'), optional_parameters=optional_parameters, ) elif self.config.send_multipart_sar: resp = yield protocol.submit_csm_sar( vumi_message_id, to_addr.encode('ascii'), short_message=text.encode(self.config.submit_sm_encoding), data_coding=self.config.submit_sm_data_coding, source_addr=from_addr.encode('ascii'), optional_parameters=optional_parameters, ) elif self.config.send_multipart_udh: resp = yield protocol.submit_csm_udh( vumi_message_id, to_addr.encode('ascii'), short_message=text.encode(self.config.submit_sm_encoding), data_coding=self.config.submit_sm_data_coding, source_addr=from_addr.encode('ascii'), optional_parameters=optional_parameters, ) else: resp = yield protocol.submit_sm( vumi_message_id, to_addr.encode('ascii'), short_message=text.encode(self.config.submit_sm_encoding), data_coding=self.config.submit_sm_data_coding, source_addr=from_addr.encode('ascii'), optional_parameters=optional_parameters, ) returnValue(resp)
class DeliverShortMessageProcessor(default.DeliverShortMessageProcessor): CONFIG_CLASS = DeliverShortMessageProcessorConfig def __init__(self, transport, config): super(DeliverShortMessageProcessor, self).__init__(transport, config) self.transport = transport self.redis = transport.redis self.config = self.CONFIG_CLASS(config, static=True) self.session_manager = SessionManager( self.redis, max_session_length=self.config.max_session_length) @inlineCallbacks def handle_deliver_sm_ussd(self, pdu, pdu_params, pdu_opts): service_op = pdu_opts['ussd_service_op'] mica_session_identifier = pdu_opts['user_message_reference'] vumi_session_identifier = make_vumi_session_identifier( pdu_params['source_addr'], mica_session_identifier) session_event = 'close' if service_op == '01': # PSSR request. Let's assume it means a new session. session_event = 'new' ussd_code = pdu_params['short_message'] content = None yield self.session_manager.create_session(vumi_session_identifier, ussd_code=ussd_code) elif service_op == '17': # PSSR response. This means session end. session_event = 'close' session = yield self.session_manager.load_session( vumi_session_identifier) ussd_code = session['ussd_code'] content = None yield self.session_manager.clear_session(vumi_session_identifier) else: session_event = 'continue' session = yield self.session_manager.load_session( vumi_session_identifier) ussd_code = session['ussd_code'] content = pdu_params['short_message'] # This is stashed on the message and available when replying # with a `submit_sm` session_info = { 'session_identifier': mica_session_identifier, } decoded_msg = self.dcs_decode(content, pdu_params['data_coding']) result = yield self.handle_short_message_content( source_addr=pdu_params['source_addr'], destination_addr=ussd_code, short_message=decoded_msg, message_type='ussd', session_event=session_event, session_info=session_info) returnValue(result)
class DeliverShortMessageProcessor(default.DeliverShortMessageProcessor): CONFIG_CLASS = DeliverShortMessageProcessorConfig # NOTE: these keys are hexidecimal because of python-smpp encoding # quirkiness ussd_service_op_map = { '01': 'new', '12': 'continue', '81': 'close', # user abort } def __init__(self, transport, config): super(DeliverShortMessageProcessor, self).__init__(transport, config) self.transport = transport self.redis = transport.redis self.config = self.CONFIG_CLASS(config, static=True) self.session_manager = SessionManager( self.redis, max_session_length=self.config.max_session_length) @inlineCallbacks def handle_deliver_sm_ussd(self, pdu, pdu_params, pdu_opts): service_op = pdu_opts['ussd_service_op'] # 6D uses its_session_info as follows: # # * First 15 bit: dialog id (i.e. session id) # * Last bit: end session (1 to end, 0 to continue) its_session_number = int(pdu_opts['its_session_info'], 16) end_session = bool(its_session_number % 2) sixdee_session_identifier = "%04x" % (its_session_number & 0xfffe) vumi_session_identifier = make_vumi_session_identifier( pdu_params['source_addr'], sixdee_session_identifier) if end_session: session_event = 'close' else: session_event = self.ussd_service_op_map.get(service_op) if session_event == 'new': # PSSR request. Let's assume it means a new session. ussd_code = pdu_params['short_message'] content = None yield self.session_manager.create_session( vumi_session_identifier, ussd_code=ussd_code) elif session_event == 'close': session = yield self.session_manager.load_session( vumi_session_identifier) ussd_code = session['ussd_code'] content = None yield self.session_manager.clear_session(vumi_session_identifier) else: if session_event != 'continue': log.warning(('Received unknown %r ussd_service_op, ' 'assuming continue.') % (service_op,)) session_event = 'continue' session = yield self.session_manager.load_session( vumi_session_identifier) ussd_code = session['ussd_code'] content = pdu_params['short_message'] # This is stashed on the message and available when replying # with a `submit_sm` session_info = { 'ussd_service_op': service_op, 'session_identifier': sixdee_session_identifier, } decoded_msg = self.dcs_decode(content, pdu_params['data_coding']) result = yield self.handle_short_message_content( source_addr=pdu_params['source_addr'], destination_addr=ussd_code, short_message=decoded_msg, message_type='ussd', session_event=session_event, session_info=session_info) returnValue(result)
class SubmitShortMessageProcessor(default.SubmitShortMessageProcessor): CONFIG_CLASS = SubmitShortMessageProcessorConfig def __init__(self, transport, config): super(SubmitShortMessageProcessor, self).__init__(transport, config) self.transport = transport self.redis = transport.redis self.config = self.CONFIG_CLASS(config, static=True) self.session_manager = SessionManager( self.redis, max_session_length=self.config.max_session_length) @inlineCallbacks def handle_outbound_message(self, message, protocol): to_addr = message['to_addr'] from_addr = message['from_addr'] text = message['content'] vumi_message_id = message['message_id'] session_event = message['session_event'] transport_type = message['transport_type'] optional_parameters = {} if transport_type == 'ussd': continue_session = (session_event != TransportUserMessage.SESSION_CLOSE) session_info = message['transport_metadata'].get( 'session_info', {}) mica_session_identifier = session_info.get('session_identifier', '') vumi_session_identifier = make_vumi_session_identifier( to_addr, mica_session_identifier) optional_parameters.update({ 'ussd_service_op': ('02' if continue_session else '17'), 'user_message_reference': (str(mica_session_identifier).zfill(2)), }) if not continue_session: yield self.session_manager.clear_session( vumi_session_identifier) if self.config.send_long_messages: resp = yield protocol.submit_sm_long( vumi_message_id, to_addr.encode('ascii'), long_message=text.encode(self.config.submit_sm_encoding), data_coding=self.config.submit_sm_data_coding, source_addr=from_addr.encode('ascii'), optional_parameters=optional_parameters, ) elif self.config.send_multipart_sar: resp = yield protocol.submit_csm_sar( vumi_message_id, to_addr.encode('ascii'), short_message=text.encode(self.config.submit_sm_encoding), data_coding=self.config.submit_sm_data_coding, source_addr=from_addr.encode('ascii'), optional_parameters=optional_parameters, ) elif self.config.send_multipart_udh: resp = yield protocol.submit_csm_udh( vumi_message_id, to_addr.encode('ascii'), short_message=text.encode(self.config.submit_sm_encoding), data_coding=self.config.submit_sm_data_coding, source_addr=from_addr.encode('ascii'), optional_parameters=optional_parameters, ) else: resp = yield protocol.submit_sm( vumi_message_id, to_addr.encode('ascii'), short_message=text.encode(self.config.submit_sm_encoding), data_coding=self.config.submit_sm_data_coding, source_addr=from_addr.encode('ascii'), optional_parameters=optional_parameters, ) returnValue(resp)
class TestSessionManager(VumiTestCase): @inlineCallbacks def setUp(self): self.persistence_helper = self.add_helper(PersistenceHelper()) self.manager = yield self.persistence_helper.get_redis_manager() yield self.manager._purge_all() # Just in case self.sm = SessionManager(self.manager) self.add_cleanup(self.sm.stop) @inlineCallbacks def test_active_sessions(self): def get_sessions(): return self.sm.active_sessions().addCallback(lambda s: sorted(s)) def ids(): return get_sessions().addCallback(lambda s: [x[0] for x in s]) self.assertEqual((yield ids()), []) yield self.sm.create_session("u1") self.assertEqual((yield ids()), ["u1"]) # 10 seconds later yield self.sm.create_session("u2", created_at=time.time() + 10) self.assertEqual((yield ids()), ["u1", "u2"]) s1, s2 = yield get_sessions() self.assertTrue(s1[1]["created_at"] < s2[1]["created_at"]) @inlineCallbacks def test_schedule_session_expiry(self): self.sm.max_session_length = 60.0 yield self.sm.create_session("u1") @inlineCallbacks def test_create_and_retrieve_session(self): session = yield self.sm.create_session("u1") self.assertEqual(sorted(session.keys()), ["created_at"]) self.assertTrue(time.time() - float(session["created_at"]) < 10.0) loaded = yield self.sm.load_session("u1") self.assertEqual(loaded, session) @inlineCallbacks def test_create_clears_existing_session(self): session = yield self.sm.create_session("u1", foo="bar") self.assertEqual(sorted(session.keys()), ["created_at", "foo"]) loaded = yield self.sm.load_session("u1") self.assertEqual(loaded, session) session = yield self.sm.create_session("u1", bar="baz") self.assertEqual(sorted(session.keys()), ["bar", "created_at"]) loaded = yield self.sm.load_session("u1") self.assertEqual(loaded, session) @inlineCallbacks def test_save_session(self): test_session = {"foo": 5, "bar": "baz"} yield self.sm.create_session("u1") yield self.sm.save_session("u1", test_session) session = yield self.sm.load_session("u1") self.assertTrue(session.pop("created_at") is not None) # Redis saves & returns all session values as strings self.assertEqual(session, dict([map(str, kvs) for kvs in test_session.items()]))
def setup_transport(self): yield super(InfobipTransport, self).setup_transport() r_prefix = "infobip:%s" % (self.transport_name,) session_timeout = int(self.config.get("ussd_session_timeout", 600)) self.session_manager = yield SessionManager.from_redis_config( self.r_config, r_prefix, session_timeout)
def get_session_manager(self, config): return SessionManager( self.get_redis(config).sub_manager('session'), max_session_length=config.max_session_length)
class SubmitShortMessageProcessor(default.SubmitShortMessageProcessor): CONFIG_CLASS = SubmitShortMessageProcessorConfig # NOTE: these values are hexidecimal because of python-smpp encoding # quirkiness ussd_service_op_map = { 'continue': '02', 'close': '17', # end } def __init__(self, transport, config): super(SubmitShortMessageProcessor, self).__init__(transport, config) self.transport = transport self.redis = transport.redis self.config = self.CONFIG_CLASS(config, static=True) self.session_manager = SessionManager( self.redis, max_session_length=self.config.max_session_length) @inlineCallbacks def handle_outbound_message(self, message, service): to_addr = message['to_addr'] from_addr = message['from_addr'] text = message['content'] if text is None: text = u"" vumi_message_id = message['message_id'] session_event = message['session_event'] transport_type = message['transport_type'] optional_parameters = {} if transport_type == 'ussd': continue_session = (session_event != TransportUserMessage.SESSION_CLOSE) session_info = message['transport_metadata'].get( 'session_info', {}) mica_session_identifier = session_info.get('session_identifier', '') vumi_session_identifier = make_vumi_session_identifier( to_addr, mica_session_identifier) service_op = self.ussd_service_op_map[( 'continue' if continue_session else 'close')] optional_parameters.update({ 'ussd_service_op': service_op, 'user_message_reference': (str(mica_session_identifier).zfill(2)), }) if not continue_session: yield self.session_manager.clear_session( vumi_session_identifier) resp = yield self.send_short_message( service, vumi_message_id, to_addr.encode('ascii'), text.encode(self.config.submit_sm_encoding), data_coding=self.config.submit_sm_data_coding, source_addr=from_addr.encode('ascii'), optional_parameters=optional_parameters) returnValue(resp)