Example #1
0
def job_update_computers_status(bot: Bot, job: Job):
    user_data = job.context

    if Session.get_from(user_data).connected:
        update_computers_status(user_data)
    else:
        job.schedule_removal()
    def reminder_message(self, bot: Bot, job: Job):
        event = self.repository.find_by_id(job.context['event_id'])

        if not event:
            job.schedule_removal()
            return

        bot.sendMessage(job.context['chat_id'], text=EventInline.create_event_message(event),
                        reply_markup=InlineKeyboardMarkup(
                            inline_keyboard=EventInline.create_reply_keyboard(event)),
                        parse_mode=ParseMode.HTML)
    def test_schedule_removal(self):
        j0 = Job(self.job1, 0.1)
        j1 = Job(self.job1, 0.2)

        self.jq.put(j0)
        self.jq.put(Job(self.job1, 0.4))
        self.jq.put(j1)

        j0.schedule_removal()
        j1.schedule_removal()

        sleep(1)
        self.assertEqual(2, self.result)
    def test_schedule_removal(self):
        j0 = Job(self.job1, 0.1)
        j1 = Job(self.job1, 0.2)

        self.jq.put(j0)
        self.jq.put(Job(self.job1, 0.4))
        self.jq.put(j1)

        j0.schedule_removal()
        j1.schedule_removal()

        sleep(1)
        self.assertEqual(2, self.result)
Example #5
0
def job_notify_subscriber(bot: Bot, job: Job):
    chat_id = job.context['chat_id']
    city = job.context['city']
    subscriber = subscribers[chat_id]
    try:
        interesting_ads = list(filter(lambda ad: subscriber.is_interested_in(ad), current_ads[city]))
        new_ads = subscriber.review_ads(interesting_ads, city)
        for new_ad in new_ads:
            bot.sendMessage(chat_id=chat_id, text=new_ad.to_chat_message())
    except Unauthorized:
        logging.warning('unauthorized in job notify. removing job')
        subscribers.pop(chat_id)
        job.schedule_removal()
Example #6
0
    def test_warnings(self, job_queue):
        j = Job(self.job_run_once, repeat=False)
        with pytest.warns(UserWarning):
            job_queue.put(j, next_t=0)
        j.schedule_removal()
        with pytest.raises(ValueError, match='can not be set to'):
            j.repeat = True
        j.interval = 15
        assert j.interval_seconds == 15
        j.repeat = True
        with pytest.raises(ValueError, match='can not be'):
            j.interval = None
        j.repeat = False
        with pytest.raises(ValueError, match='must be of type'):
            j.interval = 'every 3 minutes'
        j.interval = 15
        assert j.interval_seconds == 15

        with pytest.raises(ValueError, match='argument should be of type'):
            j.days = 'every day'
        with pytest.raises(ValueError, match='The elements of the'):
            j.days = ('mon', 'wed')
        with pytest.raises(ValueError, match='from 0 up to and'):
            j.days = (0, 6, 12, 14)
Example #7
0
class EXPbot:
    def __init__(self, telegram):
        self.updater = Updater(telegram)
        self.j = self.updater.job_queue
        self.alert = {}
        self.currissue = {}
        self.curmsg = {}
        self.current_work = {}
        dp = self.updater.dispatcher
        dp.add_handler(CommandHandler('redmine', self.redmine))
        dp.add_handler(CommandHandler('jenkins', self.jenkins))
        dp.add_handler(CommandHandler('auth', self.auth, pass_args=True))
        dp.add_handler(CallbackQueryHandler(self.filter_for_buttons))
        #dp.add_handler(CallbackQueryHandler(self.filter_for_inline))
        #dp.add_handler(InlineQueryHandler(self.inline_search))
        #dp.add_handler(ChosenInlineResultHandler(self.inline_picture))
        #dp.add_handler(MessageHandler([Filters.text], self.command_filter))
        #dp.add_handler(MessageHandler([Filters.command], self.unknow))

    def logger_wrap(self, message, command):
        user = message.from_user
        logger.info(u'%s from %s @%s %s' %
                    (message.text[0:20], user.first_name, user.username,
                     user.last_name))

    @staticmethod
    def put_user(user, key, value):
        user.__setattr__(key, value)
        db.session.commit()

    def auth(self, bot, update, args):
        user = db.query(User).filter_by(
            user_id=str(update.message.from_user.id)).first()
        if user.user_auth == 1:
            return True
        elif args == ['123']:
            EXPbot.put_user(user, 'user_auth', 1)
            bot.sendMessage(update.message.chat_id,
                            text='Ok',
                            parse_mode=ParseMode.MARKDOWN)
            return True
        else:
            bot.sendMessage(update.message.chat_id,
                            text=u'Необходима авторизация',
                            parse_mode=ParseMode.MARKDOWN)
            return False

    def redmine(self, bot, update):
        try:
            db.add(
                User(str(update.message.from_user.id),
                     update.message.from_user.name, 0))
            db.commit()
        except:
            db.rollback()
        self.logger_wrap(update.message, 'redmine')
        self.alert[str(update.message.from_user.id)] = 0
        if not self.auth(bot, update, args=None):
            return
        chat_id = str(update.message.chat_id)
        try:
            bot.editMessageReplyMarkup(chat_id=chat_id, message_id=self.curmsg)
        except:
            pass
        text = EXPbot.redmine_info()
        self.currissue[str(update.message.from_user.id)] = text
        keyboard = EXPbot.do_keyboard('redmine')
        bot.sendMessage(chat_id,
                        text=text,
                        parse_mode=ParseMode.MARKDOWN,
                        reply_markup=keyboard)

    @staticmethod
    def redmine_info():
        t_time = datetime.date.today()
        redmine = Redmine('http://help.heliosoft.ru', key='')
        issues_open_prov = redmine.issue.filter(project_id='experium',
                                                status_id='3',
                                                cf_19='me')
        issues_open_me = redmine.issue.filter(assigned_to_id='me')
        issues_open_all_totay = redmine.issue.filter(project_id='experium',
                                                     created_on=str(t_time))
        issues_open_all_totay_up = redmine.issue.filter(project_id='experium',
                                                        updated_on=str(t_time))
        text = ''
        text += u'*НА ПРОВЕРКУ!!!*\n'
        for t in issues_open_prov:
            text += (
                u'[%s](http://help.heliosoft.ru/issues/%s) %s %s\n' %
                (str(t.id), str(t.id), str(t.status), str(t).decode('utf8')))
        text += u'*\n\nЗАДАЧИ НА МНЕ!!!*\n'
        for t in issues_open_me:
            text += (
                u'[%s](http://help.heliosoft.ru/issues/%s) %s %s\n' %
                (str(t.id), str(t.id), str(t.status), str(t).decode('utf8')))
        text += (u'\n\n*Тикеты, добавленные за %s:*\n' %
                 str(t_time.strftime('%d %b %Y')))
        for t in issues_open_all_totay:
            text += (
                u'[%s](http://help.heliosoft.ru/issues/%s) %s %s\n' %
                (str(t.id), str(t.id), str(t.status), str(t).decode('utf8')))
        text += (u'\n\n*Тикеты, обновленные за %s:*\n' %
                 str(t_time.strftime('%d %b %Y')))
        for t in issues_open_all_totay_up:
            text += (
                u'[%s](http://help.heliosoft.ru/issues/%s) %s %s\n' %
                (str(t.id), str(t.id), str(t.status), str(t).decode('utf8')))
        return text

    def jenkins(self, bot, update):
        try:
            db.add(
                User(str(update.message.from_user.id),
                     update.message.from_user.name, 0))
            db.commit()
        except:
            db.rollback()
        try:
            chat_id = str(update.message.chat_id)
            if not self.auth(bot, update, args=None):
                return
            try:
                bot.editMessageReplyMarkup(chat_id=chat_id,
                                           message_id=self.curmsg)
            except:
                pass
            self.curmsg[str(
                update.message.from_user.id)] = str(update.message.message_id +
                                                    1)
            callback = 0
        except:
            chat_id = str(update.callback_query.message.chat_id)
            self.curmsg[str(update.callback_query.from_user.id
                            )] = update.callback_query.message.message_id
            callback = 1
        self.J = Jenkins('http://buildsrv.experium.ru/',
                         username="",
                         password="")
        text = u'*Список доступных работ:*\n'
        buttons = []
        for t in self.J.keys():
            if 'Experium' in t:
                buttons.append([
                    telegram.InlineKeyboardButton(text=str(t),
                                                  callback_data=str(t))
                ])
        keyboard = telegram.InlineKeyboardMarkup(buttons)
        if callback == 0:
            bot.sendMessage(chat_id,
                            text=text,
                            parse_mode=ParseMode.MARKDOWN,
                            reply_markup=keyboard)
        else:
            bot.editMessageText(text=text,
                                chat_id=chat_id,
                                message_id=self.curmsg[str(
                                    update.callback_query.from_user.id)],
                                parse_mode=ParseMode.MARKDOWN,
                                reply_markup=keyboard)

    def jenkins_work_info(self, cur_job):
        text = ''
        j = self.J.get_job(cur_job)
        try:
            q = j.get_last_good_build()
            text = u'*%s*\n\n*Последняя удачная сборка:*\n%s SVN REV - %s' % (
                cur_job, str(q), str(q._get_svn_rev()))
            changes = q.get_changeset_items()
            text += u'\n\n*Изменения:*'
            for t in range(len(changes)):
                text += re.sub(
                    r'#?(\d{4,}\b)',
                    r'[#\1](http://help.heliosoft.ru/issues/\1)',
                    u'\n*%s)*%s' %
                    (str(t + 1), str(changes[t]['msg']).decode('utf8')))
        except:
            pass
        if j.is_running():
            text += u'\n\n*Текущее состояние:* идет сборка'
        else:
            text += u'\n\n*Текущее состояние:* сборка не выполняется'
        return text

    def filter_for_buttons(self, bot, update):
        query = update.callback_query
        if query.data == 'jenkins_build':
            j = self.J.get_job(self.current_work[str(query.from_user.id)])
            if not j.is_queued_or_running():
                self.J.build_job(self.current_work[str(query.from_user.id)])
            self.job_jenkins_build = Job(self.build_monitor,
                                         10.0,
                                         repeat=True,
                                         context=[
                                             query.message.chat_id,
                                             self.current_work[str(
                                                 query.from_user.id)]
                                         ])
            self.j.put(self.job_jenkins_build)
            bot.answerCallbackQuery(callback_query_id=str(query.id),
                                    text=u'Сборка запущена')
        elif query.data == 'jenkins_close':
            self.jenkins(bot, update)
        elif query.data == 'redmine_alert':
            if self.alert[str(query.from_user.id)] == 0:
                bot.answerCallbackQuery(callback_query_id=str(query.id),
                                        text=u'Уведомления включены')
                self.alert[str(query.from_user.id)] = 1
                self.job_redmine_alert = Job(
                    self.issue_monitor,
                    60.0,
                    repeat=True,
                    context=[query.message.chat_id, query.from_user.id])
                self.j.put(self.job_redmine_alert)
            else:
                bot.answerCallbackQuery(callback_query_id=str(query.id),
                                        text=u'Уведомления выключены')
                self.alert[str(query.from_user.id)] = 0
        else:
            text = self.jenkins_work_info(query.data)
            self.current_work[str(query.from_user.id)] = query.data
            keyboard = EXPbot.do_keyboard('jenkins')
            bot.editMessageText(text=text,
                                chat_id=query.message.chat_id,
                                message_id=self.curmsg[str(
                                    query.from_user.id)],
                                parse_mode=ParseMode.MARKDOWN,
                                reply_markup=keyboard)

    @staticmethod
    def do_keyboard(flag):
        if flag == 'jenkins':
            keyboard = telegram.InlineKeyboardMarkup([[
                telegram.InlineKeyboardButton(text=u'⚒',
                                              callback_data='jenkins_build'),
                #telegram.InlineKeyboardButton(text=u'📢', callback_data='jenkins_update'),
                telegram.InlineKeyboardButton(text=u'❌',
                                              callback_data='jenkins_close')
            ]])
        if flag == 'redmine':
            keyboard = telegram.InlineKeyboardMarkup([[
                telegram.InlineKeyboardButton(text=u'📢',
                                              callback_data='redmine_alert')
            ]])
        return keyboard

    def build_monitor(self, bot, job):
        j = self.J.get_job(job.context[1])
        if j.is_running():
            return
        else:
            bot.sendMessage(
                chat_id=job.context[0],
                text=u'Сборка %s завершена\n\n%s' %
                (job.context[1], self.jenkins_work_info(job.context[1])),
                parse_mode=ParseMode.MARKDOWN)
            self.job_jenkins_build.schedule_removal()

    def issue_monitor(self, bot, job):
        if self.alert[str(job.context[1])] == 1:
            try:
                new_text = EXPbot.redmine_info()
                if self.currissue[str(job.context[1])] != new_text:
                    cd = difflib.ndiff(
                        u''.join(self.currissue[str(
                            job.context[1])]).splitlines(),
                        u''.join(new_text).splitlines())
                    final = '\n'.join(list(cd))
                    bot.sendMessage(chat_id=job.context[0],
                                    text=u'📢\n%s' % final,
                                    parse_mode=ParseMode.MARKDOWN)
                    self.currissue[str(job.context[1])] = new_text
            except:
                pass
        else:
            self.job_redmine_alert.schedule_removal()

    def unknow(self, bot, update):
        self.logger_wrap(update.message, 'unknow')

    def error(self, bot, update, error):
        self.logger_wrap(update.message, 'error')
        logger.warn('Update "%s" caused error "%s"' % (update, error))

    def idle(self):
        self.updater.start_polling()
        self.updater.idle()
Example #8
0
def remove_job(context: CallbackContext, chat_id: int, job: Job):
    job_key = JOB_TO_CHAT_DATA_KEY[job.name]
    context._dispatcher.chat_data[chat_id][JOBS][job_key].remove(job)
    job.schedule_removal()
Example #9
0
def caps(bot, update, args):
    text_caps = ' '.join(args).upper()
    bot.sendMessage(chat_id=update.message.chat_id, text=text_caps)


def callback_minute(bot, job):
    bot.sendMessage(chat_id='99601112', text='One message every minute')


job_minute = Job(callback_minute, 60.0)
j.put(job_minute, next_t=0.0)


def callback_30(bot, job):
    bot.sendMessage(chat_id='99601112', text='A single message with 30s delay')


j.put(Job(callback_30, 30.0, repeat=False))

job_minute.enabled = False  # Temporarily disable this job
job_minute.schedule_removal()  # Remove this job completely

caps_handler = CommandHandler('caps', caps, pass_args=True)
dispatcher.add_handler(caps_handler)
echo_handler = MessageHandler(Filters.text, echo)
dispatcher.add_handler(echo_handler)
start_handler = CommandHandler('start', start)
dispatcher.add_handler(start_handler)

updater.start_polling()
class TelegramBot(object):
    def __init__(self):
        self.config = configparser.ConfigParser()
        self.config.read('bot.config')
        self.updater = Updater(token=self.config.get('KEYS', 'bot_api'))
        self.dispatcher = self.updater.dispatcher
        self.job_queue = self.updater.job_queue
        self.garage_expire_request = None  # job to handle expiring the codes if a garage is not selected
        self.logger = None
        self._init_logging()
        # Garage door params
        self.garage_door_base_url = None
        self.garage_door_user_pass = None
        self._set_garage_door_parameters()

    def _set_garage_door_parameters(self):
        hostname = self.config.get('GARAGE', 'hostname')
        port = self.config.get('GARAGE', 'port')

        user = self.config.get('GARAGE', 'username')
        password = self.config.get('GARAGE', 'password')

        self.garage_door_base_url = 'http://{0}:{1}'.format(hostname, port)
        self.garage_door_user_pass = (user, password)

    def _init_logging(self):
        log_file_path = os.path.join(
            self.config.get('ADMIN', 'log_file_location'), 'telegram-bot.log')
        logging.basicConfig(level=logging.INFO,
                            format='%(message)s',
                            filename=log_file_path)
        logger = logging.getLogger(__name__)

        self.logger = wrap_logger(
            logger,
            processors=[TimeStamper(), format_exc_info,
                        JSONRenderer()],
            script="telegram_bot")

    def sender_is_admin(self, sender_id):
        sender_id = int(sender_id)
        admin_id = int(self.config.get('ADMIN', 'id'))
        if sender_id == admin_id:
            return True
        self.logger.warning("User {0} is not an admin".format(sender_id),
                            sender_id=sender_id,
                            admin_id=admin_id)
        return False

    def send_nagios_alerts(self, bot, job):
        """
        Retrieves alerts and sends them
        """
        admin_id = self.config.get('ADMIN', 'id')
        self.logger.info("Getting alerts from db")

        hostname = self.config.get('ALERTS', 'hostname')
        url = 'http://{0}/get_nagios_unsent_alerts'.format(hostname)
        r = requests.get(url)
        if r.status_code != 200:
            bot.sendMessage(
                chat_id=sender_id,
                text='An exception occured while acknowledging alert',
                reply_keyboard=None)
            return ConversationHandler.END

        unsent_alerts = r.json()
        if not unsent_alerts:  # Nothing to do
            return ConversationHandler.END

        message_str = """"""
        for one_alert in unsent_alerts:  # No need to check count as server limits it

            alert_id = one_alert['id']
            url = 'http://{0}/update_alert/{1}/SENT'.format(hostname, alert_id)
            r = requests.get(url)
            if r.status_code != 200:
                bot.sendMessage(
                    chat_id=sender_id,
                    text='An exception occured while updating alert status',
                    reply_keyboard=None)

            host_name = one_alert['hostname']
            service_name = one_alert['hostname']
            message_str += "Alert ID: {}".format(str(one_alert['id']))
            message_str += one_alert['message_text']
            if one_alert['acknowledgable']:
                acknowledgeable_alerts_cache[alert_id] = (
                    host_name, service_name)  # Add to dictionary to track
            message_str += "--------------------\n"

        if message_str:
            message_str += "{0} messages sent".format(len(unsent_alerts))

        # If there are alerts than can be acknowledged, add the keyboard to acknowledge
        reply_keyboard = None
        if acknowledgeable_alerts_cache:  # We have some alerts that can be acknowledged
            options = []  # Stores the keys for the keyboard reply
            for alert_id, (host, service) in list(
                    acknowledgeable_alerts_cache.items()):
                key_string = "acknowledge {alert_id} | {host}  {service}".format(
                    alert_id=alert_id, host=host, service=service)
                options.append([key_string])  # Store the key for the keyboard

            # Send the message with the keyboard
            reply_keyboard = ReplyKeyboardMarkup(options,
                                                 one_time_keyboard=True)
        bot.sendMessage(chat_id=admin_id,
                        text=message_str,
                        reply_markup=reply_keyboard)

        self.logger.info("Finished sending {} alerts".format(
            len(unsent_alerts)))

    def power_status(self, bot, update, args):
        arguments_to_use = ['status', 'timeleft']
        complete_output = ""

        self.logger.info("Got request to check power status",
                         sender_id=update.message.chat_id)
        for one_arg in arguments_to_use:
            command_to_run = [
                '/usr/lib/nagios/plugins/check_apcupsd {0}'.format(one_arg)
            ]
            complete_output += subprocess.check_output(command_to_run,
                                                       shell=True)

        bot.sendMessage(chat_id=update.message.chat_id, text=complete_output)
        self.logger.info("Sent message for power status",
                         sender_id=update.message.chat_id)

    def acknowledge_alert(self, bot, update, groups):
        """
        Given a string in the form of "acknowledge <ID> | <SOME DESC>" this sends the appropriate nagios commands
        :param bot:
        :param update:
        :param groups: tuple of regex group
        :return:
        """
        sender_id = update.message.chat_id
        self.logger.info("Got request to acknowledge id {0}".format(
            groups, sender_id=sender_id))
        if not groups:
            # did not pass us an alert id
            bot.sendMessage(chat_id=update.message.chat_id,
                            text="No alert specified")
            return ConversationHandler.END

        try:
            _, alert_id = groups[0].split(' ')
            alert_id = alert_id.strip()
        except IndexError:
            bot.sendMessage(
                chat_id=update.message.chat_id,
                text="Invalid string {} passed to acknowledge".format(
                    groups[0]))
            return ConversationHandler.END

        if alert_id not in acknowledgeable_alerts_cache:
            bot.sendMessage(chat_id=update.message.chat_id,
                            text="Did not find id {} in cache".format(
                                groups[0]))
            self.logger.error("Attempted to access id {}. Cache had {}".format(
                alert_id, acknowledgeable_alerts_cache))
            return ConversationHandler.END

        hostname = self.config.get('ALERTS', 'hostname')
        url = 'http://{0}/acknowledge/{1}'.format(hostname, alert_id)
        r = requests.get(url)
        if r.status_code != 200:
            bot.sendMessage(
                chat_id=sender_id,
                text='An exception occured while acknowledging alert',
                reply_keyboard=None)
            return ConversationHandler.END

        text = "Successfully scheduled downtime for id {0} for 1 day".format(
            alert_id)
        self.logger.info(text)
        bot.sendMessage(chat_id=sender_id, text=text, reply_keyboard=None)

        return ConversationHandler.END

    def _get_garage_position(self, garage_name='all'):
        # Returns whether the garage is open or closed
        request_url = '{0}/garage/status/{1}'.format(self.garage_door_base_url,
                                                     garage_name)
        r = requests.get(request_url, auth=self.garage_door_user_pass)
        if r.status_code == 200:
            return r.json()

        return []

    # Action for operating the garage
    def garage(self, bot, update):
        return_message = """"""
        sender_id = update.message.chat_id
        # Gives menu to select which garage to open
        if not self.sender_is_admin(sender_id):
            self.logger.warning("Unauthorized user", sender_id=sender_id)
            bot.sendMessage(chat_id=sender_id, text='Not authorized')
            return ConversationHandler.END

        options = []  # Stores the keys for the keyboard reply
        self.logger.info("Got request to open garage.", sender_id=sender_id)

        garage_statuses = self._get_garage_position()
        if not garage_statuses:
            bot.sendMessage(
                chat_id=sender_id,
                text='An exception occured while getting garage status',
                reply_keyboard=None)
            return ConversationHandler.END

        # Handle the reponse and creation of the keyboard
        return_message += "Pick a garage \n"
        for one_garage_dict in garage_statuses:
            garage_name = one_garage_dict['garage_name']
            current_status = one_garage_dict['status']
            return_message += ': '.join([
                garage_name, current_status, one_garage_dict['status_time']
            ]) + '\n'

            # Determine whether this can be opened or closed
            if not one_garage_dict['error']:
                action = 'CLOSE' if current_status == 'OPEN' else 'OPEN'
                key_string = ' '.join(['confirm', action, str(garage_name)])
                options.append([key_string])  # Store the key for the keyboard
        options.append(["CANCEL GARAGE"])  # Store the key for the keyboard

        # Send the message with the keyboard
        reply_keyboard = ReplyKeyboardMarkup(options, one_time_keyboard=True)
        bot.sendMessage(chat_id=sender_id,
                        text=return_message,
                        reply_markup=reply_keyboard)

        # Expires request so that the conversation is not open forever
        def expire_request(bot, job):
            bot.sendMessage(chat_id=sender_id,
                            text='Timeout reached. Please start again',
                            reply_keyboard=None)
            return ConversationHandler.END

        # Add job to expire request
        self.garage_expire_request = Job(expire_request, 15.0, repeat=False)
        self.job_queue.put(self.garage_expire_request)

        # Set the conversation to go to the next state
        return GarageConversationState.CONFIRM

    def confirm_garage_action(self, bot, update):
        sender_id = update.message.chat_id

        # See if there is a pending job to expire the request. Stop running it if there is
        if self.garage_expire_request is not None:
            self.garage_expire_request.schedule_removal()
            self.garage_expire_request = None

        if not self.sender_is_admin(update.message.chat_id):
            bot.sendMessage(chat_id=sender_id, text='Not authorized')
            return ConversationHandler.END

        action, garage_name = update.message.text.split(' ')[1:]
        request_url = '{0}/garage/control/{1}/{2}'.format(
            self.garage_door_base_url, garage_name, action)

        r = requests.get(request_url, auth=self.garage_door_user_pass)
        if r.status_code != 200:
            bot.sendMessage(
                chat_id=sender_id,
                text='An exception occured while sending the {0} command'.
                format(action),
                reply_keyboard=None)
            return ConversationHandler.END

        response = r.json()

        self.logger.info("User triggered opening of garage",
                         sender_id=sender_id,
                         garage_name=garage_name)

        bot.sendMessage(chat_id=sender_id, text=response['status'])

        def send_current_status(bot, job):
            response = self._get_garage_position(garage_name)
            text_to_send = ': '.join([
                garage_name, response[0]['status'], response[0]['status_time']
            ])

            bot.sendMessage(chat_id=sender_id, text=text_to_send)

        # Wait to allow the door to move and send the status back
        self.job_queue.put(Job(send_current_status, 15.0, repeat=False))

        return ConversationHandler.END

    def get_gemini_quote(self, quote_id):
        mapping = {"ETH": "ethusd", "BTC": "btcusd"}
        quote_name = mapping[quote_id]

        GEMINI_STR = "GEMINI_STR"
        if cache.get(GEMINI_STR):
            self.logger.info("Got hit for cache", exchange='GEMINI')
            return cache.get(GEMINI_STR)

        url = 'https://api.gemini.com/v1/pubticker/{0}'.format(quote_name)
        try:
            result = json.load(urllib.request.urlopen(url))
        except Exception as e:
            self.logger.exception("Could not get quote from exchange",
                                  exc_info=e,
                                  exchange='GEMINI')
            return "Gemini", "", "Could not get quote from Gemini"

        bid_price = result['bid']
        ask_price = result['ask']

        quote_details = "Gemini", bid_price, ask_price

        # Store string into cache
        cache[GEMINI_STR] = quote_details

        return quote_details

    def get_gdax_quote(self, quote_name):
        GDAX_STR = "GDAX_STR"
        mapping = {"ETH": "ETH-USD", "BTC": "BTC-USD"}

        quote_name = mapping[quote_name]
        if cache.get(GDAX_STR):
            self.logger.info("Got hit for cache", exchange='GDAX')
            return cache.get(GDAX_STR)

        url = 'https://api.gdax.com/products/{0}/book'.format(quote_name)
        try:
            result = json.load(urllib.request.urlopen(url))
        except Exception as e:
            self.logger.exception("Could not get quote from exchange",
                                  exc_info=e,
                                  exchange='GDAX')
            return "GDAX", "", "Could not get quote from GDAX"

        bid_price, bid_amount, _ = result['bids'][0]
        ask_price, ask_amount, _ = result['asks'][0]

        quote_details = "GDAX", bid_price, ask_price

        # Store string into cache
        cache[GDAX_STR] = quote_details

        return quote_details

    def get_coinmarketcap_data(self):
        COINMARKETCAP_STR = "COINMARKETCAP_STR"
        if market_cap_cache.get(COINMARKETCAP_STR):
            self.logger.info("Got hit for cache", exchange='COINMARKETCAP')
            return market_cap_cache.get(COINMARKETCAP_STR)

        url = 'https://api.coinmarketcap.com/v1/global/'
        try:
            result = json.load(urllib.request.urlopen(url))
        except Exception as e:
            self.logger.exception("Could not get quote from exchange",
                                  exc_info=e,
                                  exchange='COINMARKETCAP')
            return "CoinMarketCap", "", "Could not get info from CoinMarketCap"

        total_market_cap = result['total_market_cap_usd']
        bitcoin_percent_dominance = result['bitcoin_percentage_of_market_cap']

        # Get volume of ETH and BTC
        url = 'https://api.coinmarketcap.com/v1/ticker/{0}'
        tickers_to_get = ['bitcoin', 'ethereum']
        results = []

        for ticker in tickers_to_get:
            try:
                results.append(
                    json.load(urllib.request.urlopen(url.format(ticker))))
            except Exception as e:
                self.logger.exception("Could not get quote from exchange",
                                      exc_info=e,
                                      exchange='COINMARKETCAP')
                return "CoinMarketCap", "", "Could not get info from CoinMarketCap"

        btc_result, ethereum_result = results
        btc_volume = btc_result[0]['24h_volume_usd']
        eth_volume = ethereum_result[0]['24h_volume_usd']
        eth_btc_volume_ratio = float(eth_volume) / float(btc_volume)

        final_result = (total_market_cap, bitcoin_percent_dominance,
                        eth_btc_volume_ratio)

        market_cap_cache[COINMARKETCAP_STR] = final_result

        return final_result

    def get_current_quotes(self, bot, update, args):
        chat_id = update.message.chat_id

        if not self.sender_is_admin(chat_id):
            bot.sendMessage(chat_id=chat_id, text='Not authorized')
            return ConversationHandler.END

        quote_name = "ETH" if not args else str(args[0])

        prices_to_get = [self.get_gdax_quote, self.get_gemini_quote]
        string_to_send = "Time: {0}\n".format(
            datetime.datetime.today().strftime("%Y-%m-%d %H:%m:%S"))

        for one_exchange in prices_to_get:
            exchange_name, bid_price, ask_price = one_exchange(quote_name)
            string_to_send += "{0} : Bid: {1} Ask: {2}\n".format(
                exchange_name, bid_price, ask_price)

        total_marketcap, btc_dominance, eth_btc_volume_ratio = self.get_coinmarketcap_data(
        )

        string_to_send += "MarketCap: {0:d}B BTC Dom: {1} ETH/BTC Vol Ratio:{2:.2f}".format(
            int(total_marketcap / 1000000000), btc_dominance,
            eth_btc_volume_ratio)

        self.logger.info("Sending quote: {0}".format(string_to_send),
                         sender_id=chat_id,
                         exchange='GDAX')
        bot.sendMessage(chat_id=chat_id, text=string_to_send)
        return ConversationHandler.END

    def unknown_handler(self, bot, update):
        if update.message:
            chat_id = update.message.chat_id
        else:
            chat_id = update.channel.chat_id
        self.logger.warn("UNHANDLED MESSAGE".format(update.message.chat_id),
                         sender_id=chat_id,
                         message_dict=update.to_dict())

        bot.sendMessage(chat_id=chat_id, text="Did not understand message")

        return ConversationHandler.END  # Make sure to end any conversations

    def heartbeat_handler(self, bot, update):
        # Sents a signal to the nagios server that we are still up
        dict_to_send = [{
            'return_code': "0",
            'plugin_output': "Telegram bot is up",
            'service_description': "Telegram Bot Available",
            'hostname': 'monitoring-station',
        }]
        url = self.config.get('ALERTS', 'passive_alerts_endpoint')

        requests.post(url, json=dict_to_send)

    def setup(self):
        self.logger.info("Starting up TelegramBot")

        power_status_handler = CommandHandler('powerstatus',
                                              self.power_status,
                                              pass_args=True)
        self.dispatcher.add_handler(power_status_handler)

        acknowledge_alert_handler = RegexHandler('^(acknowledge \d+)',
                                                 self.acknowledge_alert,
                                                 pass_groups=True)
        self.dispatcher.add_handler(acknowledge_alert_handler)

        # Handler for opening the garage
        garage_menu_handler = ConversationHandler(
            entry_points=[
                CommandHandler('garage', self.garage),
                RegexHandler('^(Garage|garage)', self.garage),
                RegexHandler('^(Ga)', self.garage)
            ],
            states={
                GarageConversationState.CONFIRM:
                [MessageHandler(ConfirmFilter(), self.confirm_garage_action)]
            },
            fallbacks=[
                MessageHandler(Filters.command | Filters.text,
                               self.unknown_handler)
            ])
        self.dispatcher.add_handler(garage_menu_handler)

        # GDAX quote handler
        gdax_quote_handler = CommandHandler('quotes',
                                            self.get_current_quotes,
                                            pass_args=True)
        self.dispatcher.add_handler(gdax_quote_handler)

        # Add handler for messages we arent handling
        unknown_handler = MessageHandler(Filters.command | Filters.text,
                                         self.unknown_handler)
        self.dispatcher.add_handler(unknown_handler)

        # Create the job to check if we have any nagios alerts to send
        self.job_queue.run_repeating(self.send_nagios_alerts, 90.0)

        # Add job to alert nagios server we are up
        if int(self.config.get('ALERTS', 'heartbeat')) == 1:
            self.logger.info("Enabling heartbeat handler for nagios")
            self.job_queue.run_repeating(self.heartbeat_handler, 120.0)

    def run(self):
        self.setup()
        self.updater.start_polling()
        self.updater.idle()