def listen_slack(): """ Listens to the slack callback and if it is a Slack event then processes it and extracts chanel_id, slack_user_id, timestamp, message and calls desired method in a thread. If it's not a Slack event then method considers the callback as a Slack callback authentication and returns the quoted challenge code if exists. :rtype: str """ event = request.json.get('event') if event: current_timestamp = event.get('ts') channel_id = request.json.get('event').get('channel') slack_user_id = request.json.get('event').get('user') message = request.json.get('event').get('text') if message and channel_id and slack_user_id: logger.info( "Message slack:%s, Current_timestamp: %s, Slack User ID: %s" % (message, current_timestamp, slack_user_id)) run_slack_communication_handler.delay(channel_id, message, slack_user_id, current_timestamp) return 'HTTP_200_OK' challenge = request.json.get('challenge') if challenge: if request.json.get('token') == app.config['SLACK_VERIFICATION_TOKEN']: return quote(challenge) return 'HTTP_200_OK'
def receive_mail(): """ End point which listens mail gun callbacks :rtype: str """ message = request.form.get('stripped-text') sender = request.form.get('sender') subject = request.form.get('subject') if message and sender: logger.info('Received email body: ' + message + ', Sender: ' + sender) email_bot.handle_communication(sender, subject, message) return 'HTTP_200_OK'
def reply(self, chanel_id, msg, slack_client): """ Replies to user on specified slack channel :param SlackClient slack_client: Slack Client RTM object :param str chanel_id: Slack channel id :param str msg: Message received to bot :rtype: None """ logger.info('slack reply: %s' % msg) slack_client.api_call("chat.postMessage", channel=chanel_id, text=msg, set_active=True)
def set_bot_state_active(): """ Receives bot_token and perform an activity using that token to let Slack servers know that bot is online :rtype: json """ bot_token = request.json.get('bot_token') if bot_token: print bot_token slack_client = SlackClient(bot_token) slack_client.rtm_connect() logger.info('Slack bot status online for token %s' % bot_token) return ApiResponse({"response": "OK"}) raise InvalidUsage("No token found in request body")
def reply(self, response, recipient): """ Replies to the user through sms :param str response: Response message from bot :param str recipient: User's mobile number :rtype: None """ # Twilio sms text doesn't seem to support'[' and ']' response = response.replace('[', '(') response = response.replace(']', ')') if len(response) > self.standard_sms_length: tokens = response.split('\n') total_segments, dict_of_segments = self.get_total_sms_segments(tokens) for segment_indexer in dict_of_segments: segment = dict_of_segments.get(segment_indexer) + \ "("+str(segment_indexer)+"/"+str(total_segments) + ")" message = self.twilio_client.messages.create(to=recipient, from_=self.twilio_number, body=segment) logger.info('Twilio response status: ' + message.status) logger.info('message body:' + segment) else: message = self.twilio_client.messages.create(to=recipient, from_=self.twilio_number, body=response) logger.info('SMS Reply: ' + response) logger.info('Twilio response status: ' + message.status)
def is_response_time_more_than_usual(self, user_message): """ Checks if handler is going to take long time for processing :param string user_message: User message :rtype: bool """ if len(user_message.split()) > MIN_WORDS_IN_QUESTION: add_candidates_questions = self.list_of_questions[ ADD_CANDIDATE_FROM_URL:ADD_CANDIDATE_FROM_URL + 2] match_ratio = self.match_question(user_message, add_candidates_questions) if match_ratio >= AVERAGE_QUESTION_MATCH_RATIO: logger.info("Responding before processing") return True return False
def get_bot_id(self, slack_client): """ Gets bot Id :param SlackClient slack_client: SlackClient object :rtype: str|None """ api_call = slack_client.api_call("users.list") if api_call.get('ok'): # retrieve all users so we can find our bot users = api_call.get('members') for user in users: if 'name' in user and self.bot_name == user.get('name'): logger.info("Bot ID for %s is %s" % (user['name'], user.get('id'))) temp_at_bot = '<@%s>' % user.get('id') return temp_at_bot raise NotFoundError("could not find bot user with the name %s" % self.bot_name)
def authenticate_user(self, slack_user_id, message, channel_id): """ Authenticates user :param str slack_user_id: User's slack Id :param str message: User's message :param str channel_id: Slack channel Id :rtype: tuple (True|False, None|str, None|Slack_client, TalentbotAuth.slack_user_id|None) :return: is_authenticated, user message with bot id stripped, slack_client, user_id """ talentbot_auth = TalentbotAuth.get_talentbot_auth( slack_user_id=slack_user_id) if talentbot_auth: slack_user_token = talentbot_auth.bot_token user_id = talentbot_auth.user_id if slack_user_token and user_id: slack_client = SlackClient(slack_user_token) user = User.get_by_id(user_id) if user.is_disabled: is_authenticated, user_id = True, None return is_authenticated, message, slack_client, user_id try: if talentbot_auth.bot_id: at_bot = '<@%s>' % talentbot_auth.bot_id presence = slack_client.api_call('users.getPresence') if not presence.get('online'): self.set_bot_state_active(talentbot_auth.bot_token) else: at_bot = self.get_bot_id(slack_client) except NotFoundError as error: logger.error(error.message) is_authenticated, slack_client, user_id = False, None, None return is_authenticated, message, slack_client, user_id # Slack channel Id starts with 'C' if it is a channel and # Start's with 'D' if it's a private message is_channel = channel_id[0] == 'C' is_private_message = channel_id[0] == 'D' at_bot_colon = '%s:' % at_bot if (at_bot in message or at_bot_colon in message and is_channel) \ or (is_private_message and slack_user_id != at_bot): return True, message.replace(at_bot, ''), slack_client, user_id logger.info("Not authenticated") is_authenticated, slack_client, user_id = False, None, None return is_authenticated, message, slack_client, user_id
def match_question(message, questions, partial=True): """ Matches user message with questions and returns max matched ratio :param bool partial: Boolean parameter to determine if partial matching is required or not :param string message: User message :param string|list questions: :rtype: positive """ if isinstance(questions, list): max_matched_ratio = ZERO for question in questions: temp_ratio = fuzz.partial_ratio( question, message) if partial else fuzz.ratio( question, message) if temp_ratio > max_matched_ratio: max_matched_ratio = temp_ratio return max_matched_ratio message_lower = message.lower() match_ratio = fuzz.partial_ratio( questions, message_lower) if partial else fuzz.ratio( questions, message_lower) logger.info("%s : %d%% matched" % (message, match_ratio)) return match_ratio
def empty_users_state(): """ This method removes saved users' states from redis db to avoid deadlocks """ # Getting registered user phone ids user_phone_ids = TalentbotAuth.get_all_user_phone_ids() # Getting first entry of tuples user_phone_ids = list(*zip(*user_phone_ids)) # Removing None from list user_phone_ids = filter(partial(is_not, None), user_phone_ids) """ If there is no user_phone_id available in table we get [None], that's why I'm comparing user_phone_ids[0] instead of user_phone_ids because [None] != [] """ if user_phone_ids: # Getting user ids against registered users' phone ids user_ids = UserPhone.get_user_ids_by_phone_ids(user_phone_ids) # Extracting data from tuple user_ids = list(*zip(*user_ids)) for user_id in user_ids: redis_store.delete("bot-pg-%d" % user_id) redis_store.delete("%dredis_lock" % user_id) logger.info("Flushed user states successfully")
def reply(self, recipient, subject, message): """ Sends Email to the recipient via mailgun API :param str recipient: Email sender :param str subject: Subject of email :param str message: Email response message :return: response from mailgun API :rtype: response """ html = '<html><img src="' + self.bot_image + '" style="width: 9%; display:'\ ' inline;"><h5 style="display:'\ ' table-cell; vertical-align:'\ ' top;margin-left: 1%;">' + message +\ '</h5></html>' response = send_email( source=app.config[TalentConfigKeys.BOT_SOURCE_EMAIL], subject=subject, body=None, html_body=html, email_format='html', to_addresses=[recipient]) logger.info('Mail reply "%s", to %s' % (message, recipient)) return response
def set_bot_state_active(self, bot_token): """ Sets Slack bot state active :param str bot_token: bot token :rtype: None """ token = User.generate_jw_token() header = {'Authorization': token, 'Content-Type': 'application/json'} job_config = { "frequency": 144, "task_type": "periodic", "start_datetime": datetime.utcnow(), "end_datetime": datetime.utcnow() + relativedelta(days=1), "url": TalentBotApiUrl.SLACK_BOT_STATUS, "task_name": bot_token, "post_data": { "bot_token": bot_token } } # Getting task by name try: task = http_request('get', SchedulerApiUrl.TASK_NAME % bot_token, headers=header) if task.status_code == 200: task_json = json.loads(task.text) http_request('delete', SchedulerApiUrl.TASK % task_json['task']['id'], headers=header) except Exception as e: logger.info( "No Scheduler task exists with name %s creating new one" % bot_token) response = http_request('POST', SchedulerApiUrl.TASKS, headers=header, data=json.dumps(job_config))
""" Run Celery Worker For Celery to run from command line, script runs as separate process with celery command Usage: open terminal cd to talent-flask-services directory Run the following command to start celery worker: $ celery -A talentbot_service.celery_app worker --concurrency=4 --loglevel=info """ # Service Specific from talentbot_service.modules.constants import QUEUE_NAME from talentbot_service.common.talent_celery import CELERY_WORKER_ARGS from talentbot_service import celery_app, logger, app logger.info("Starting Celery worker for:%s" % app.name) celery_app.start(argv=CELERY_WORKER_ARGS + [QUEUE_NAME] + ['-n', 'talentbot_service'])