def deliver_sm(self, *args, **kwargs): message_type = kwargs.get('message_type', 'sms') message = { 'message_id': kwargs['message_id'], 'to_addr': kwargs['destination_addr'], 'from_addr': kwargs['source_addr'], 'content': kwargs['short_message'], 'transport_type': message_type, 'transport_metadata': {}, } if message_type == 'ussd': session_event = { 'new': TransportUserMessage.SESSION_NEW, 'continue': TransportUserMessage.SESSION_RESUME, 'close': TransportUserMessage.SESSION_CLOSE, }[kwargs['session_event']] message['session_event'] = session_event session_info = kwargs.get('session_info') message['transport_metadata']['session_info'] = session_info log.msg("PUBLISHING INBOUND: %s" % (message,)) # TODO: This logs messages that fail to serialize to JSON # Usually this happens when an SMPP message has content # we can't decode (e.g. data_coding == 4). We should # remove the try-except once we handle such messages # better. return self.publish_message(**message).addErrback(log.err)
def get_shortened_url(self, msg, config, url): if isinstance(url, unicode): url = url.encode('utf-8') user_token = self.hash_user(msg.user()) headers = { 'User-Agent': 'vumi-wikipedia-http-request', 'content-type': 'application/json' } shortening_api_url = config.shortening_api_url.geturl() auth_header, clean_api_url = self.get_basic_auth_header( shortening_api_url ) if auth_header: headers.update(auth_header) payload = {'long_url': url, 'user_token': user_token} api_url = urljoin(clean_api_url, 'create') response = yield http_request_full( api_url, json.dumps(payload), headers, method='PUT') try: result = json.loads(response.delivered_body) returnValue(result['short_url']) except Exception, e: log.msg("Error reading API response: %s %r" % ( response.code, response.delivered_body)) log.err() raise APIError(e)
def setup_transport(self): config = self.get_static_config() log.msg('Starting SMPP Transport for: %s' % (config.twisted_endpoint,)) default_prefix = '%s@%s' % (config.system_id, config.transport_name) redis_prefix = config.split_bind_prefix or default_prefix self.redis = (yield TxRedisManager.from_config( config.redis_manager)).sub_manager(redis_prefix) self.dr_processor = config.delivery_report_processor( self, config.delivery_report_processor_config) self.deliver_sm_processor = config.deliver_short_message_processor( self, config.deliver_short_message_processor_config) self.submit_sm_processor = config.submit_short_message_processor( self, config.submit_short_message_processor_config) self.sequence_generator = self.sequence_class(self.redis) self.message_stash = SmppMessageDataStash(self.redis, config) self.throttled = None self._throttled_message_ids = [] self._unthrottle_delayedCall = None self.factory = self.factory_class(self) self.service = self.start_service(self.factory) self.tps_counter = 0 self.tps_limit = config.mt_tps if config.mt_tps > 0: self.mt_tps_lc = LoopingCall(self.reset_mt_tps) self.mt_tps_lc.clock = self.clock self.mt_tps_lc.start(1, now=True) else: self.mt_tps_lc = None
def finish_expired_request(self, request_id, request): """ Called on requests that timed out. """ del self._requests[request_id] log.msg('Timing out on response for %s' % request.session['from_addr']) request.deferred.callback(self.response_for_error())
def handle_inbound_text_message(self, request, wc_msg): double_delivery = yield self.check_for_double_delivery( request, wc_msg.msg_id) if double_delivery: log.msg('WeChat double delivery of message: %s' % (wc_msg.msg_id,)) return lock = yield self.mark_as_seen_recently(wc_msg.msg_id) if not lock: log.msg('Unable to get lock for message id: %s' % (wc_msg.msg_id,)) return config = self.get_static_config() if config.embed_user_profile: user_profile = yield self.get_user_profile(wc_msg.from_user_name) else: user_profile = {} mask = yield self.get_addr_mask(wc_msg.from_user_name) msg = yield self.publish_message( content=wc_msg.content, from_addr=wc_msg.from_user_name, to_addr=self.mask_addr(wc_msg.to_user_name, mask), timestamp=datetime.fromtimestamp(int(wc_msg.create_time)), transport_type=self.transport_type, transport_metadata={ 'wechat': { 'FromUserName': wc_msg.from_user_name, 'ToUserName': wc_msg.to_user_name, 'MsgType': 'text', 'MsgId': wc_msg.msg_id, 'UserProfile': user_profile, } }) returnValue(msg)
def handle_event(self, event, handler_config): sms_copy = handler_config['sms_copy'] conversation_key = handler_config['conversation_key'] account_key = event['account_key'] from_addr = event['content']['from_addr'] user_api = self.get_user_api(account_key) contact = yield self.find_contact(account_key, from_addr) if not contact: log.msg('Unable to find contact for %s' % (from_addr,)) return content = (sms_copy['swahili'] if contact.extra['language'] == '2' else sms_copy['english']) conversation = yield user_api.get_wrapped_conversation( conversation_key) yield conversation.dispatch_command( 'send_message', account_key, conversation.key, command_data={ 'batch_id': conversation.batch.key, 'to_addr': from_addr, 'content': content, 'msg_options': {}, } )
def handle_raw_inbound_message(self, request_id, request): values, errors = self.get_field_values(request, self.EXPECTED_FIELDS) if errors: log.msg('Unhappy incoming message: %r' % (errors,)) self.finish_request( request_id, json.dumps(errors), code=http.BAD_REQUEST) return to_addr = values["ussdServiceCode"] from_addr = values["msisdn"] session_event = yield self.session_event_for_transaction( values["transactionId"]) yield self.publish_message( message_id=request_id, content=values["ussdRequestString"], to_addr=to_addr, from_addr=from_addr, provider='dmark', session_event=session_event, transport_type=self.transport_type, transport_metadata={ 'dmark_ussd': { 'transaction_id': values['transactionId'], 'transaction_time': values['transactionTime'], 'creation_time': values['creationTime'], } })
def switch_to_counters(self, batch_id): """ Actively switch a batch from the old ``zcard()`` based approach to the new ``redis.incr()`` counter based approach. """ uses_counters = yield self.uses_counters(batch_id) if uses_counters: log.msg('Batch %r has already switched to counters.' % (batch_id, )) return # NOTE: Under high load this may result in the counter being off # by a few. Considering this is a cache that is to be # reconciled we're happy for that to be the case. inbound_count = yield self.count_inbound_message_keys(batch_id) outbound_count = yield self.count_outbound_message_keys(batch_id) # We do `*_count or None` because there's a chance of getting back # a None if this is a new batch that's not received any traffic yet. yield self.redis.set(self.inbound_count_key(batch_id), inbound_count or 0) yield self.redis.set(self.outbound_count_key(batch_id), outbound_count or 0) yield self.truncate_inbound_message_keys(batch_id) yield self.truncate_outbound_message_keys(batch_id)
def make_request(self, params): log.msg("Making HTTP request: %s" % (repr(params))) config = self.get_static_config() return http_request_full(config.outbound_url, urlencode(params), method='POST', headers=self.headers)
def render_(self, request): log.msg("Send request: %s" % (request,)) request.setHeader("content-type", "application/json; charset=utf-8") d = self.application.handle_raw_outbound_message(request) d.addCallback(lambda msgs: self.finish_request(request, msgs)) d.addErrback(lambda f: self.fail_request(request, f)) return NOT_DONE_YET
def _make_call(self, params): params.setdefault('format', 'json') url = '%s?%s' % (self.url, urlencode(params)) if isinstance(url, unicode): url = url.encode('utf-8') headers = {'User-Agent': self.user_agent} if self.gzip: headers['Accept-Encoding'] = 'gzip' if self.PRINT_DEBUG: print "\n=====\n\n%s /?%s\n" % ('GET', url.split('?', 1)[1]) response = yield http_request_full(url, '', headers, method='GET', timeout=self.api_timeout, agent_class=redirect_agent_builder) if self.PRINT_DEBUG: print response.delivered_body print "\n=====" try: returnValue(json.loads(response.delivered_body)) except Exception, e: log.msg("Error reading API response: %s %r" % (response.code, response.delivered_body)) log.err() raise APIError(e)
def handle_raw_inbound_message(self, message_id, request): values, errors = self.get_field_values(request, self.EXPECTED_FIELDS) channel = values.get('channel') if channel is not None and channel not in self.CHANNEL_LOOKUP.values(): errors['unsupported_channel'] = channel if errors: log.msg('Unhappy incoming message: %s' % (errors,)) yield self.finish_request(message_id, json.dumps(errors), code=http.BAD_REQUEST) return self.emit("AppositTransport receiving inbound message from " "%(from)s to %(to)s" % values) yield self.publish_message( transport_name=self.transport_name, message_id=message_id, content=values['content'], from_addr=values['from'], to_addr=values['to'], provider='apposit', transport_type=self.TRANSPORT_TYPE_LOOKUP[channel], transport_metadata={'apposit': {'isTest': values['isTest']}}) yield self.finish_request( message_id, json.dumps({'message_id': message_id}))
def handle_outbound_message(self, message): metadata = message['transport_metadata']['mtn_nigeria_ussd'] try: self.validate_outbound_message(message) except CodedXmlOverTcpError as e: log.msg(e) yield self.publish_nack(message['message_id'], "%s" % e) yield self.factory.client.send_error_response( metadata.get('session_id'), message.payload.get('in_reply_to'), e.code) return log.msg( 'MtnNigeriaUssdTransport sending outbound message: %s' % message) end_session = ( message['session_event'] == TransportUserMessage.SESSION_CLOSE) yield self.send_response( message_id=message['message_id'], session_id=metadata['session_id'], request_id=metadata['requestId'], star_code=metadata['starCode'], client_id=metadata['clientId'], msisdn=message['to_addr'], user_data=message['content'].encode(self.ENCODING), end_session=end_session)
def handle_outbound_message(self, message): metadata = message['transport_metadata']['mtn_nigeria_ussd'] try: self.validate_outbound_message(message) except CodedXmlOverTcpError as e: log.msg(e) yield self.publish_nack(message['message_id'], "%s" % e) yield self.factory.client.send_error_response( metadata.get('session_id'), message.payload.get('in_reply_to'), e.code) return log.msg('MtnNigeriaUssdTransport sending outbound message: %s' % message) end_session = ( message['session_event'] == TransportUserMessage.SESSION_CLOSE) yield self.send_response(message_id=message['message_id'], session_id=metadata['session_id'], request_id=metadata['requestId'], star_code=metadata['starCode'], client_id=metadata['clientId'], msisdn=message['to_addr'], user_data=message['content'].encode( self.ENCODING), end_session=end_session)
def handle_raw_inbound_message(self, message_id, request): values, errors = self.get_field_values(request, self.EXPECTED_FIELDS) channel = values.get('channel') if channel is not None and channel not in self.CHANNEL_LOOKUP.values(): errors['unsupported_channel'] = channel if errors: log.msg('Unhappy incoming message: %s' % (errors, )) yield self.finish_request(message_id, json.dumps(errors), code=http.BAD_REQUEST) return self.emit("AppositTransport receiving inbound message from " "%(from)s to %(to)s" % values) yield self.publish_message( transport_name=self.transport_name, message_id=message_id, content=values['content'], from_addr=values['from'], to_addr=values['to'], provider='apposit', transport_type=self.TRANSPORT_TYPE_LOOKUP[channel], transport_metadata={'apposit': { 'isTest': values['isTest'] }}) yield self.finish_request(message_id, json.dumps({'message_id': message_id}))
def switch_to_counters(self, batch_id): """ Actively switch a batch from the old ``zcard()`` based approach to the new ``redis.incr()`` counter based approach. """ uses_counters = yield self.uses_counters(batch_id) if uses_counters: log.msg('Batch %r has already switched to counters.' % ( batch_id,)) return # NOTE: Under high load this may result in the counter being off # by a few. Considering this is a cache that is to be # reconciled we're happy for that to be the case. inbound_count = yield self.count_inbound_message_keys(batch_id) outbound_count = yield self.count_outbound_message_keys(batch_id) # We do `*_count or None` because there's a chance of getting back # a None if this is a new batch that's not received any traffic yet. yield self.redis.set(self.inbound_count_key(batch_id), inbound_count or 0) yield self.redis.set(self.outbound_count_key(batch_id), outbound_count or 0) yield self.truncate_inbound_message_keys(batch_id) yield self.truncate_outbound_message_keys(batch_id)
def handle_raw_inbound_message(self, request_id, request): values, errors = self.get_field_values(request, self.EXPECTED_FIELDS) if errors: log.msg('Unhappy incoming message: %r' % (errors, )) self.finish_request(request_id, json.dumps(errors), code=http.BAD_REQUEST) return to_addr = values["ussdServiceCode"] from_addr = values["msisdn"] session_event = yield self.session_event_for_transaction( values["transactionId"]) yield self.publish_message(message_id=request_id, content=values["ussdRequestString"], to_addr=to_addr, from_addr=from_addr, provider='dmark', session_event=session_event, transport_type=self.transport_type, transport_metadata={ 'dmark_ussd': { 'transaction_id': values['transactionId'], 'transaction_time': values['transactionTime'], 'creation_time': values['creationTime'], } })
def handle_raw_inbound_message(self, msgid, request): parts = request.path.split('/') session_id = parts[-2] session_method = parts[-1] session_event, session_handler_name, sends_json = ( self.METHOD_TO_HANDLER.get(session_method, (TransportUserMessage.SESSION_NONE, "handle_infobip_error", False))) session_handler = getattr(self, session_handler_name) req_content = request.content.read() log.msg("Incoming message: %r" % (req_content,)) if sends_json: try: req_data = json.loads(req_content) except ValueError: # send bad JSON to error handler session_handler = self.handle_infobip_error req_data = {"error": "Invalid JSON"} else: req_data = {} message_dict = yield session_handler(msgid, session_id, req_data) if message_dict is not None: transport_metadata = {'session_id': session_id} message_dict.setdefault("message_id", msgid) message_dict.setdefault("session_event", session_event) message_dict.setdefault("content", None) message_dict["transport_name"] = self.transport_name message_dict["transport_type"] = self.config.get('transport_type', 'ussd') message_dict["transport_metadata"] = transport_metadata self.publish_message(**message_dict)
def stopWorker(self): log.msg("HeartBeat: stopping worker") if self._task: self._task.stop() self._task = None yield self._task_done self._redis.close_manager()
def handle_enquire_link(self, pdu): if pdu['header']['command_status'] == 'ESME_ROK': log.msg("enquire_link OK") sequence_number = pdu['header']['sequence_number'] pdu_resp = EnquireLinkResp(sequence_number) self.send_pdu(pdu_resp) else: log.msg("enquire_link NOT OK: %r" % (pdu, ))
def make_request(self, params): config = self.get_static_config() url = '%s?%s' % (config.outbound_url, urlencode(params)) log.msg("Making HTTP request: %s" % (url, )) return http_request_full(url, '', method='POST', agent_class=self.agent_factory)
def connectionMade(self): self.state = 'OPEN' log.msg('STATE: %s' % (self.state)) seq = yield self.get_next_seq() pdu = self.BIND_PDU(seq, **self.bind_params) log.msg(pdu.get_obj()) self.send_pdu(pdu) self.schedule_lose_connection(self.CONNECTED_STATE)
def reset_mt_tps(self): if self.throttled and self.need_mt_throttling(): if not self.service.is_bound(): # We don't have a bound SMPP connection, so try again later. log.msg("Can't stop throttling while unbound, trying later.") return self.reset_mt_throttle_counter() self.stop_throttling(quiet=True)
def handle_enquire_link(self, pdu): if pdu['header']['command_status'] == 'ESME_ROK': log.msg("enquire_link OK") sequence_number = pdu['header']['sequence_number'] pdu_resp = EnquireLinkResp(sequence_number) self.send_pdu(pdu_resp) else: log.msg("enquire_link NOT OK: %r" % (pdu,))
def login(self): params = [ ('requestId', self.gen_request_id()), ('userName', self.username), ('passWord', self.password), # plaintext passwords, yay :/ ('applicationId', self.application_id), ] self.send_packet(self.gen_session_id(), 'AUTHRequest', params) log.msg('Logging in')
def register_client(self, client): # We add our own Deferred to the client here because we only want to # fire it after we're finished with our own deregistration process. client.registration_d = Deferred() client_addr = client.getAddress() log.msg("Registering client connected from %r" % client_addr) self._clients[client_addr] = client self.send_inbound_message(client, None, TransportUserMessage.SESSION_NEW)
def lose_unbound_connection(self, required_state): if self.state != required_state: log.msg('Breaking connection due to binding delay, %s != %s\n' % ( self.state, required_state)) self._lose_conn = None self.transport.loseConnection() else: log.msg('Successful bind: %s, cancelling bind timeout' % ( self.state))
def lose_unbound_connection(self, required_state): if self.state != required_state: log.msg('Breaking connection due to binding delay, %s != %s\n' % (self.state, required_state)) self._lose_conn = None self.transport.loseConnection() else: log.msg('Successful bind: %s, cancelling bind timeout' % (self.state))
def log_action(self, msg, action, **kw): provider = msg.get('provider') or '' log_parts = [ 'WIKI', self.hash_user(msg.user()), msg['transport_name'], msg['transport_type'], provider, action, log_escape(msg['content']), ] + [u'%s=%s' % (k, log_escape(v)) for (k, v) in kw.items()] log.msg(u'\t'.join(unicode(s) for s in log_parts).encode('utf8'))
def handle_outbound(self, config, msg, conn_name): log.msg("Processing outbound message: %s" % (msg,)) user_id = msg['to_addr'] session_event = msg['session_event'] session_manager = yield self.session_manager(config) session = yield session_manager.load_session(user_id) if session and (session_event == TransportUserMessage.SESSION_CLOSE): yield session_manager.clear_session(user_id) yield self.publish_outbound(msg)
def handle_state_selected(self, config, session, msg): active_endpoint = session['active_endpoint'] if active_endpoint not in self.target_endpoints(config): log.msg(("Router configuration change forced session " "termination for user %s" % msg['from_addr'])) yield self.publish_error_reply(msg, config) returnValue((None, {})) else: yield self.publish_inbound(msg, active_endpoint) returnValue((self.STATE_SELECTED, {}))
def startWorker(self): log.msg('Starting a %s dispatcher with config: %s' % (self.__class__.__name__, self.config)) yield self.setup_endpoints() yield self.setup_router() yield self.setup_transport_publishers() yield self.setup_exposed_publishers() yield self.setup_transport_consumers() yield self.setup_exposed_consumers()
def log(self, api, msg, level): conv = self.app_worker.conversation_for_api(api) campaign_key = conv.user_account.key conversation_key = conv.key internal_msg = "[Account: %s, Conversation: %s] %s" % ( campaign_key, conversation_key, msg) log.msg(internal_msg, logLevel=level) yield self.log_manager.add_log(campaign_key, conversation_key, msg, level)
def is_authenticated(self, request): config = self.get_static_config() if self.EXPECTED_AUTH_FIELDS.issubset(request.args): username = request.args['userid'][0] password = request.args['password'][0] auth = (username == config.airtel_username and password == config.airtel_password) if not auth: log.msg('Invalid authentication credentials: %s:%s' % ( username, password)) return auth
def is_authenticated(self, request): config = self.get_static_config() if self.EXPECTED_AUTH_FIELDS.issubset(request.args): username = request.args['userid'][0] password = request.args['password'][0] auth = (username == config.airtel_username and password == config.airtel_password) if not auth: log.msg('Invalid authentication credentials: %s:%s' % (username, password)) return auth
def start_periodic_enquire_link(self): if not self.authenticated: log.msg("Heartbeat could not be started, client not authenticated") return self.periodic_enquire_link.clock = self.clock d = self.periodic_enquire_link.start( self.enquire_link_interval, now=True) log.msg("Heartbeat started") return d
def handle_login_response(self, session_id, params): try: self.validate_packet_fields(params, self.LOGIN_RESPONSE_FIELDS) except CodedXmlOverTcpError as e: self.disconnect() self.handle_error(session_id, params.get('requestId'), e) return log.msg("Client authentication complete.") self.authenticated = True self.start_periodic_enquire_link()
def handle_inbound(self, config, msg, conn_name): log.msg("Handling inbound: %s" % (msg, )) try: contact = yield self.get_contact_for_message(msg, create=False) except ContactNotFoundError: contact = None except ContactError: log.err() return endpoint = self.endpoint_for_contact(config, contact) yield self.publish_inbound(msg, endpoint)
def startWorker(self): log.msg("Garbage Worker is starting") super(GarbageWorker, self).startWorker() connection = pymongo.Connection('localhost', 27017) db = connection[self.config['database_name']] if not 'unmatchable_reply' in db.collection_names(): db.create_collection('unmatchable_reply') self.unmatchable_reply_collection = db['unmatchable_reply'] #if not 'shortcodes' in db.collection_names(): #db.create_collection('shortcodes') self.shortcodes_collection = db['shortcodes'] self.templates_collection = db['templates']
def handle_inbound(self, config, msg, conn_name): log.msg("Handling inbound: %s" % (msg,)) try: contact = yield self.get_contact_for_message(msg, create=False) except ContactNotFoundError: contact = None except ContactError: log.err() return endpoint = self.endpoint_for_contact(config, contact) yield self.publish_inbound(msg, endpoint)
def delivery_report(self, message_id, message_state): delivery_status = self.delivery_status(message_state) message_id = yield self.r_get_id_for_third_party_id(message_id) if message_id is None: log.warning("Failed to retrieve message id for delivery report." " Delivery report from %s discarded." % self.transport_name) return log.msg("PUBLISHING DELIV REPORT: %s %s" % (message_id, delivery_status)) returnValue((yield self.publish_delivery_report( user_message_id=message_id, delivery_status=delivery_status)))
def handle_raw_inbound_message(self, msgid, request): request_body = request.content.read() log.msg("Inbound message: %r" % (request_body,)) try: body = ET.fromstring(request_body) except: log.warning("Error parsing request XML: %s" % (request_body,)) yield self.finish_request(msgid, "", code=400) return # We always get this. session_id = body.find('session_id').text status_elem = body.find('status') if status_elem is not None: # We have a status message. These are all variations on "cancel". yield self.handle_status_message(msgid, session_id) return page_id = body.find('page_id').text # They sometimes send us page_id=0 in the middle of a session. if page_id == '0' and body.find('mobile_number') is not None: # This is a new session. session = yield self.save_session( session_id, from_addr=body.find('mobile_number').text, to_addr=body.find('gate').text) # ??? session_event = TransportUserMessage.SESSION_NEW else: # This is an existing session. session = yield self.session_manager.load_session(session_id) if 'from_addr' not in session: # We have a missing or broken session. yield self.finish_request(msgid, "", code=400) return session_event = TransportUserMessage.SESSION_RESUME content = body.find('data').text transport_metadata = {'session_id': session_id} self.publish_message( message_id=msgid, content=content, to_addr=session['to_addr'], from_addr=session['from_addr'], session_event=session_event, transport_name=self.transport_name, transport_type=self.config.get('transport_type'), transport_metadata=transport_metadata, )
def handle_raw_inbound_message(self, session_id, params): # ensure the params are in the encoding we use internally params['session_id'] = session_id params = dict((k, v.decode(self.ENCODING)) for k, v in params.iteritems()) session_event = self.determine_session_event( *self.pop_fields(params, 'msgtype', 'EndofSession')) # For the first message of a session, the `user_data` field is the ussd # code. For subsequent messages, 'user_data' is the user's content. We # need to keep track of the ussd code we get in in the first session # message so we can link the correct `to_addr` to subsequent messages if session_event == TransportUserMessage.SESSION_NEW: # Set the content to none if this the start of the session. # Prevents this inbound message being mistaken as a user message. content = None to_addr = params.pop('userdata') session = yield self.session_manager.create_session( session_id, ussd_code=to_addr) else: session = yield self.session_manager.load_session(session_id) to_addr = session['ussd_code'] content = params.pop('userdata') # pop the remaining needed field (the rest is left as metadata) [from_addr] = self.pop_fields(params, 'msisdn') log.msg('MtnNigeriaUssdTransport receiving inbound message from %s ' 'to %s: %s' % (from_addr, to_addr, content)) if session_event == TransportUserMessage.SESSION_CLOSE: self.factory.client.send_data_response( session_id=session_id, request_id=params['requestId'], star_code=params['starCode'], client_id=params['clientId'], msisdn=from_addr, user_data=self.user_termination_response, end_session=True) yield self.publish_message( content=content, to_addr=to_addr, from_addr=from_addr, provider='mtn_nigeria', session_event=session_event, transport_type=self.transport_type, transport_metadata={'mtn_nigeria_ussd': params})