Esempio n. 1
0
    def __init__(self,
                 bot_token: str,
                 authorised_chat_id: str,
                 logger: logging.Logger,
                 redis: Optional[RedisApi],
                 redis_snooze_key: Optional[str],
                 redis_mute_key: Optional[str],
                 redis_node_monitor_alive_key_prefix: Optional[str],
                 redis_network_monitor_alive_key_prefix: Optional[str],
                 redis_network_monitor_last_height_key_prefix: Optional[str],
                 internal_conf: InternalConfig = InternalConf,
                 user_conf: UserConfig = UserConf) -> None:

        super().__init__(logger, redis, redis_snooze_key, redis_mute_key,
                         redis_node_monitor_alive_key_prefix,
                         redis_network_monitor_alive_key_prefix,
                         redis_network_monitor_last_height_key_prefix,
                         internal_conf, user_conf)

        # Get default snooze and mute hours
        self._default_snooze_hours = \
            internal_conf.redis_twilio_snooze_key_default_hours
        self._default_mute_hours = \
            internal_conf.redis_periodic_alive_reminder_mute_key_default_hours

        # Set up command handlers (command and respective callback function)
        command_handlers = [
            CommandHandler('start', self._start_callback),
            CommandHandler('snooze', self._snooze_callback),
            CommandHandler('mute', self._mute_callback),
            CommandHandler('unmute', self._unmute_callback),
            CommandHandler('unsnooze', self._unsnooze_callback),
            CommandHandler('status', self._status_callback),
            CommandHandler('validators', self._validators_callback),
            CommandHandler('block', self._block_callback),
            CommandHandler('tx', self._tx_callback),
            CommandHandler('help', self._help_callback),
            MessageHandler(Filters.command, self._unknown_callback)
        ]

        # Create command handler with the command handlers
        self.cmd_handler = TelegramCommandHandler(bot_token,
                                                  authorised_chat_id,
                                                  command_handlers)
Esempio n. 2
0
    def __init__(self, bot_token: str, authorised_chat_id: str,
                 logger: logging.Logger, redis: Optional[RedisApi],
                 redis_snooze_key: Optional[str],
                 redis_node_monitor_alive_key_prefix: Optional[str],
                 redis_network_monitor_alive_key_prefix: Optional[str],
                 internal_conf: InternalConfig = InternalConf,
                 user_conf: UserConfig = UserConf) -> None:

        self._logger = logger

        self._redis = redis
        self._redis_enabled = redis is not None
        self._redis_snooze_key = redis_snooze_key
        self._redis_node_monitor_alive_key_prefix = \
            redis_node_monitor_alive_key_prefix
        self._redis_network_monitor_alive_key_prefix = \
            redis_network_monitor_alive_key_prefix

        self._internal_conf = internal_conf
        self._user_conf = user_conf

        # Set up command handlers (command and respective callback function)
        command_handlers = [
            CommandHandler('start', self._start_callback),
            CommandHandler('snooze', self._snooze_callback),
            CommandHandler('unsnooze', self._unsnooze_callback),
            CommandHandler('status', self._status_callback),
            CommandHandler('validators', self._validators_callback),
            CommandHandler('block', self._block_callback),
            CommandHandler('tx', self._tx_callback),
            CommandHandler('help', self._help_callback),
            MessageHandler(Filters.command, self._unknown_callback)
        ]

        # Create command handler with the command handlers
        self.cmd_handler = TelegramCommandHandler(
            bot_token, authorised_chat_id, command_handlers)
Esempio n. 3
0
def test_telegram_commands(bot_token: str, bot_chat_id: str) -> TestOutcome:
    cmd_handler = TelegramCommandHandler(bot_token, bot_chat_id, None)
    cmd_handler.start_handling(run_in_background=True)
    print('Go ahead and send /ping to the Telegram bot.')
    input('Press ENTER once you are done sending commands...')
    print('Stopping the Telegram bot...')
    cmd_handler.stop()

    if yn_prompt('Was the testing successful? (Y/n)\n'):
        return TestOutcome.OK
    elif yn_prompt('Retry Telegram commands setup? (Y/n)\n'):
        return TestOutcome.RestartSetup
    else:
        return TestOutcome.SkipSetup
Esempio n. 4
0
class TelegramCommands(Commands):

    def __init__(self, bot_token: str, authorised_chat_id: str,
                 logger: logging.Logger, redis: Optional[RedisApi],
                 redis_snooze_key: Optional[str], redis_mute_key: Optional[str],
                 redis_node_monitor_alive_key_prefix: Optional[str],
                 redis_network_monitor_alive_key_prefix: Optional[str],
                 redis_network_monitor_last_height_key_prefix: Optional[str],
                 internal_conf: InternalConfig = InternalConf,
                 user_conf: UserConfig = UserConf) -> None:

        super().__init__(logger, redis, redis_snooze_key, redis_mute_key,
                         redis_node_monitor_alive_key_prefix,
                         redis_network_monitor_alive_key_prefix,
                         redis_network_monitor_last_height_key_prefix,
                         internal_conf, user_conf)

        # Get default snooze and mute hours
        self._default_snooze_hours = \
            internal_conf.redis_twilio_snooze_key_default_hours
        self._default_mute_hours = \
            internal_conf.redis_periodic_alive_reminder_mute_key_default_hours

        # Set up command handlers (command and respective callback function)
        command_handlers = [
            CommandHandler('start', self._start_callback),
            CommandHandler('snooze', self._snooze_callback),
            CommandHandler('mute', self._mute_callback),
            CommandHandler('unmute', self._unmute_callback),
            CommandHandler('unsnooze', self._unsnooze_callback),
            CommandHandler('status', self._status_callback),
            CommandHandler('validators', self._validators_callback),
            CommandHandler('block', self._block_callback),
            CommandHandler('tx', self._tx_callback),
            CommandHandler('help', self._help_callback),
            MessageHandler(Filters.command, self._unknown_callback)
        ]

        # Create command handler with the command handlers
        self.cmd_handler = TelegramCommandHandler(
            bot_token, authorised_chat_id, command_handlers)

    def start_listening(self) -> None:
        # Start listening for commands
        self.cmd_handler.start_handling()

    def redis_running(self) -> bool:
        try:
            self._redis.ping_unsafe()
            return True
        except (RedisError, ConnectionResetError):
            pass
        except Exception as e:
            self._logger.error('Unrecognized error when accessing Redis: %s', e)
        return False

    @staticmethod
    def formatted_reply(update: Update, reply: str):
        # Adds Markdown formatting
        update.message.reply_text(reply, parse_mode='Markdown')

    def _start_callback(self, update: Update, context: CallbackContext):
        self._logger.info('/start: update=%s, context=%s', update, context)

        # Check that authorised
        if not self.cmd_handler.authorise(update, context):
            return

        # Send welcome message
        update.message.reply_text("Welcome to the P.A.N.I.C. alerter bot!\n"
                                  "Type /help for more information.")

    def _snooze_callback(self, update: Update, context: CallbackContext):
        self._logger.info('/snooze: update=%s, context=%s', update, context)

        # Check that authorised
        if not self.cmd_handler.authorise(update, context):
            return

        # Does not make sense to snooze if twilio alerts not enabled
        if not self._user_conf.twilio_alerts_enabled:
            update.message.reply_text('Snoozing is not available given that '
                                      'Twilio calls were not set up.')
            return

        # Cannot snooze if Redis is not enabled
        if not self._redis_enabled:
            update.message.reply_text('Snoozing is not available given '
                                      'that Redis is not set up.')
            return

        # Expected: /snooze or /snooze <hours>
        message_parts = update.message.text.split(' ')
        if len(message_parts) not in [1, 2]:
            update.message.reply_text('I expected one or no values.')
            return

        # Get number of hours
        if len(message_parts) == 1:
            hours = self._default_snooze_hours
        else:  # len(message_parts) == 2
            try:
                hours = timedelta(hours=float(message_parts[1]))
            except ValueError:
                update.message.reply_text('Invalid no. of hours.')
                return

        # Set temporary Redis key signifying the snooze
        until = str(datetime.now() + hours)
        set_ret = self._redis.set_for(self._redis_snooze_key, until, hours)
        if set_ret is None:
            update.message.reply_text(
                'Snoozing unsuccessful due to an issue with '
                'Redis. Check /status to see if it is online.')
        else:
            extra_message_if_default = \
                " To snooze for a longer period of time, specify the number " \
                "of hours after the /snooze." if len(message_parts) == 1 else ""
            update.message.reply_text(
                'Calls have been snoozed for {} hours until {}.{}'
                ''.format(hours, until, extra_message_if_default))

    def _unsnooze_callback(self, update: Update, context: CallbackContext):
        self._logger.info('/unsnooze: update=%s, context=%s', update, context)

        # Check that authorised
        if not self.cmd_handler.authorise(update, context):
            return

        # Cannot unsnooze if Redis is not enabled
        if not self._redis_enabled:
            update.message.reply_text('Unsnoozing is not available given '
                                      'that Redis is not set up.')
            return

        # Cannot unsnooze if Redis not running
        if not self.redis_running():
            update.message.reply_text(
                'Unsnoozing unsuccessful due to an issue with '
                'Redis. Check /status to see if it is online.')
            return

        # Check if snooze key exists
        if not self._redis.exists(self._redis_snooze_key):
            update.message.reply_text('Twilio calls were not snoozed.')
            return

        # Unsnooze by deleting the snooze key
        self._redis.remove(self._redis_snooze_key)
        update.message.reply_text('Twilio calls have been unsnoozed.')

    def _mute_callback(self, update: Update, context: CallbackContext):
        self._logger.info('/mute: update=%s, context=%s', update, context)

        # Check that authorised
        if not self.cmd_handler.authorise(update, context):
            return

        # Does not make sense to mute if reminder not enabled
        if not self._user_conf.periodic_alive_reminder_enabled:
            update.message.reply_text('Muting is not available given that the '
                                      'periodic alive reminder is not set up.')
            return

        # Cannot mute if Redis is not enabled
        if not self._redis_enabled:
            update.message.reply_text('Muting is not available given that '
                                      'Redis is not set up.')
            return

        # Expected: /mute or /mute <hours>
        message_parts = update.message.text.split(' ')
        if len(message_parts) not in [1, 2]:
            update.message.reply_text('I expected one or no values.')
            return

        # Get number of hours
        if len(message_parts) == 1:
            hours = self._default_mute_hours
        else:  # len(message_parts) == 2
            try:
                hours = timedelta(hours=float(message_parts[1]))
            except ValueError:
                update.message.reply_text('Invalid no. of hours.')
                return

        # Set temporary Redis key signifying the mute
        until = str(datetime.now() + hours)
        set_ret = self._redis.set_for(self._redis_mute_key, until, hours)
        if set_ret is None:
            update.message.reply_text(
                'Muting unsuccessful due to an issue with '
                'Redis. Check /status to see if it is online.')
        else:
            extra_message_if_default = \
                " To mute for a longer period of time, specify the number of " \
                "hours after the /mute." if len(message_parts) == 1 else ""
            update.message.reply_text(
                'The periodic alive reminder has been muted for {} hours until '
                '{}.{}'.format(hours, until, extra_message_if_default))

    def _unmute_callback(self, update: Update, context: CallbackContext):
        self._logger.info('/unmute: update=%s, context=%s', update, context)

        # Check that authorised
        if not self.cmd_handler.authorise(update, context):
            return

        # Cannot unmute if Redis is not enabled
        if not self._redis_enabled:
            update.message.reply_text('Unmuting is not available given '
                                      'that Redis is not set up.')
            return

        # Cannot unmute if Redis not running
        if not self.redis_running():
            update.message.reply_text(
                'Unmuting unsuccessful due to an issue with '
                'Redis. Check /status to see if it is online.')
            return

        # Check if mute key exists
        if not self._redis.exists(self._redis_mute_key):
            update.message.reply_text('Periodic alive reminder was not muted.')
            return

        # Unmute by deleting the mute key
        self._redis.remove(self._redis_mute_key)
        update.message.reply_text('Periodic alive reminder has been unmuted.')

    def _status_callback(self, update: Update, context: CallbackContext):
        self._logger.info('/status: update=%s, context=%s', update, context)

        # Check that authorised
        if not self.cmd_handler.authorise(update, context):
            return

        # Cannot get status update data if Redis is not enabled
        if not self._redis_enabled:
            update.message.reply_text('Status update not available given '
                                      'that Redis is not set up.')
            return

        # If redis is not running no use trying to get status
        if not self.redis_running():
            update.message.reply_text(
                'Redis is NOT accessible! This means calls are considered not '
                'snoozed, the periodic alive reminder is not muted, and any '
                'recent update from monitors is not accessible.')
            return

        status = ""

        # Add Redis status if it is running
        if self._redis.is_live:
            status += '- Redis is running normally.\n'
        else:
            status += '- Redis is running normally but was not accessible ' \
                      'a short while ago. Recent updates might not be ' \
                      'available until the monitors detect Redis as alive. ' \
                      'Snoozing and muting might not work as expected.\n'

        # Add Twilio calls snooze state to status if Twilio enabled
        if self._user_conf.twilio_alerts_enabled:
            if self._redis.exists(self._redis_snooze_key):
                until = self._redis.get(self._redis_snooze_key).decode("utf-8")
                status += '- Twilio calls are snoozed until {}.\n'.format(until)
            else:
                status += '- Twilio calls are not snoozed.\n'

        # Add periodic alive reminder mute state to status if reminder enabled
        if self._user_conf.periodic_alive_reminder_enabled:
            if self._redis.exists(self._redis_mute_key):
                until = self._redis.get(self._redis_mute_key).decode("utf-8")
                status += '- The periodic alive reminder has ' \
                          'been muted until {}.\n'.format(until)
            else:
                status += '- The periodic alive reminder is not muted.\n'

        # Add node monitor latest updates to status
        node_monitor_keys_list = self._redis.get_keys(
            self._redis_node_monitor_alive_key_prefix + '*')
        node_monitor_names = [
            k.replace(self._redis_node_monitor_alive_key_prefix, '')
            for k in node_monitor_keys_list]
        for key, name in zip(node_monitor_keys_list, node_monitor_names):
            last_upd = self._redis.get(key).decode('utf-8')
            last_upd = last_upd.split('.')[0]  # remove seconds
            status += '- Last update from *{}*: `{}`.\n'.format(name, last_upd)

        # Add note if no latest node monitor updates
        if len(node_monitor_keys_list) == 0:
            status += '- No recent update from node monitors.\n'

        # Add network monitor latest update
        net_monitor_keys_list1 = self._redis.get_keys(
            self._redis_network_monitor_alive_key_prefix + '*')
        net_monitor_names1 = [k.replace(
            self._redis_network_monitor_alive_key_prefix, '')
            for k in net_monitor_keys_list1]
        for key, name in zip(net_monitor_keys_list1, net_monitor_names1):
            last_upd = self._redis.get(key).decode('utf-8')
            last_upd = last_upd.split('.')[0]  # remove seconds
            status += '- Last update from *{}*: `{}`.\n'.format(name, last_upd)

        # Add network monitor last height checked
        net_monitor_keys_list2 = self._redis.get_keys(
            self._redis_network_monitor_last_height_key_prefix + '*')
        net_monitor_names2 = [k.replace(
            self._redis_network_monitor_last_height_key_prefix, '')
            for k in net_monitor_keys_list2]
        for key, name in zip(net_monitor_keys_list2, net_monitor_names2):
            last_height = self._redis.get(key).decode('utf-8')
            status += '- *{}* is currently in block height {}.\n' \
                      ''.format(name, last_height)

        # Add note if no latest network monitor updates
        if len(net_monitor_keys_list1) == len(net_monitor_keys_list2) == 0:
            status += '- No recent update from network monitors.\n'

        # Send status
        TelegramCommands.formatted_reply(
            update, status[:-1] if status.endswith('\n') else status)

    def _validators_callback(self, update: Update, context: CallbackContext):
        self._logger.info('/validators: update=%s, context=%s', update, context)

        # Check that authorised
        if not self.cmd_handler.authorise(update, context):
            return

        # Send list of links to validators
        update.message.reply_text(
            'Links to validators:\n'
            '  Hubble: {}\n'
            '  BigDipper: {}\n'
            '  Stargazer: {}\n'
            '  Mintscan: {}\n'
            '  Lunie: {}'.format(
                self._internal_conf.validators_hubble_link,
                self._internal_conf.validators_big_dipper_link,
                self._internal_conf.validators_stargazer_link,
                self._internal_conf.validators_mintscan_link,
                self._internal_conf.validators_lunie_link))

    def _block_callback(self, update: Update, context: CallbackContext):
        self._logger.info('/block: update=%s, context=%s', update, context)

        # Check that authorised
        if not self.cmd_handler.authorise(update, context):
            return

        # Expected: /block <block>
        message_parts = update.message.text.split(' ')
        if len(message_parts) != 2:
            update.message.reply_text("I expected exactly one value.")
            return

        # Send list of links to specified block
        try:
            height = int(message_parts[1])
            update.message.reply_text(
                'Links to block:\n'
                '  Hubble: {}{}\n'
                '  BigDipper: {}{}\n'
                '  Stargazer: {}{}\n'
                '  Mintscan: {}{}\n'
                '  Lunie: {}{}'.format(
                    self._internal_conf.block_hubble_link_prefix, height,
                    self._internal_conf.block_big_dipper_link_prefix, height,
                    self._internal_conf.block_stargazer_link_prefix, height,
                    self._internal_conf.block_mintscan_link_prefix, height,
                    self._internal_conf.block_lunie_link_prefix, height))
        except ValueError:
            update.message.reply_text("I expected a block height.")

    def _tx_callback(self, update: Update, context: CallbackContext):
        self._logger.info('/tx: update=%s, context=%s', update, context)

        # Check that authorised
        if not self.cmd_handler.authorise(update, context):
            return

        # Expected: /tx <hash>
        message_parts = update.message.text.split(' ')
        if len(message_parts) != 2:
            update.message.reply_text("I expected exactly one value.")
            return

        # Send list of links to the specified transaction
        tx_hash = message_parts[1]
        update.message.reply_text(
            'Links to transaction:\n'
            '  Hubble: {}\n'
            '  BigDipper: {}\n'
            '  Mintscan: {}'.format(
                self._internal_conf.tx_hubble_link_prefix + str(tx_hash),
                self._internal_conf.tx_big_dipper_link_prefix + str(tx_hash),
                self._internal_conf.tx_mintscan_link_prefix + str(tx_hash)))

    def _help_callback(self, update: Update, context: CallbackContext):
        self._logger.info('/help: update=%s, context=%s', update, context)

        # Check that authorised
        if not self.cmd_handler.authorise(update, context):
            return

        # Send help message with available commands
        update.message.reply_text(
            'Hey! These are the available commands:\n'
            '  /start: welcome message\n'
            '  /ping: ping the Telegram commands handler\n'
            '  /snooze <hours>: snoozes phone calls for <hours>\n'
            '  /unsnooze: unsnoozes phone calls\n'
            '  /mute <hours>: mute periodic alive reminder for <hours>\n'
            '  /unmute: unmute periodic alive reminder\n'
            '  /status: shows status message\n'
            '  /validators: shows links to validators\n'
            '  /block <height>: shows link to specified block\n'
            '  /tx <tx-hash>: shows link to specified transaction\n'
            '  /help: shows this message')

    def _unknown_callback(self, update: Update,
                          context: CallbackContext) -> None:
        self._logger.info(
            'Received unrecognized command: update=%s, context=%s',
            update, context)

        # Check that authorised
        if not self.cmd_handler.authorise(update, context):
            return

        # Send a default message for unrecognized commands
        update.message.reply_text('I did not understand (Type /help)')
Esempio n. 5
0
class TelegramCommands:

    def __init__(self, bot_token: str, authorised_chat_id: str,
                 logger: logging.Logger, redis: Optional[RedisApi],
                 redis_snooze_key: Optional[str],
                 redis_node_monitor_alive_key_prefix: Optional[str],
                 redis_network_monitor_alive_key_prefix: Optional[str],
                 internal_conf: InternalConfig = InternalConf,
                 user_conf: UserConfig = UserConf) -> None:

        self._logger = logger

        self._redis = redis
        self._redis_enabled = redis is not None
        self._redis_snooze_key = redis_snooze_key
        self._redis_node_monitor_alive_key_prefix = \
            redis_node_monitor_alive_key_prefix
        self._redis_network_monitor_alive_key_prefix = \
            redis_network_monitor_alive_key_prefix

        self._internal_conf = internal_conf
        self._user_conf = user_conf

        # Set up command handlers (command and respective callback function)
        command_handlers = [
            CommandHandler('start', self._start_callback),
            CommandHandler('snooze', self._snooze_callback),
            CommandHandler('unsnooze', self._unsnooze_callback),
            CommandHandler('status', self._status_callback),
            CommandHandler('validators', self._validators_callback),
            CommandHandler('block', self._block_callback),
            CommandHandler('tx', self._tx_callback),
            CommandHandler('help', self._help_callback),
            MessageHandler(Filters.command, self._unknown_callback)
        ]

        # Create command handler with the command handlers
        self.cmd_handler = TelegramCommandHandler(
            bot_token, authorised_chat_id, command_handlers)

    def start_listening(self) -> None:
        # Start listening for commands
        self.cmd_handler.start_handling()

    @staticmethod
    def formatted_reply(update: Update, reply: str):
        # Adds Markdown formatting
        update.message.reply_text(reply, parse_mode='Markdown')

    def _start_callback(self, bot: Bot, update: Update):
        self._logger.info('Received /start command: bot=%s, update=%s',
                          bot, update)

        # If authorised, send welcome message
        if self.cmd_handler.authorise(bot, update):
            update.message.reply_text("Welcome to the P.A.N.I.C. alerter bot!\n"
                                      "Type /help for more information.")

    def _snooze_callback(self, bot: Bot, update: Update):
        self._logger.info('Received /snooze command: bot=%s, update=%s',
                          bot, update)

        # If authorised, snooze phone calls if Redis enabled
        if self.cmd_handler.authorise(bot, update):
            if self._redis_enabled:
                # Expected: /snooze <hours>
                message_parts = update.message.text.split(' ')
                if len(message_parts) == 2:
                    try:
                        # Get number of hours and set temporary Redis key
                        hours = timedelta(hours=float(message_parts[1]))
                        until = str(datetime.now() + hours)
                        set_ret = self._redis.set_for(
                            self._redis_snooze_key, until, hours)
                        if set_ret is None:
                            update.message.reply_text(
                                'Snoozing unsuccessful due to an issue with '
                                'Redis. Check /status to see if it is online.')
                        else:
                            update.message.reply_text(
                                'Calls have been snoozed for {} hours until {}.'
                                ''.format(hours, until))
                    except ValueError:
                        update.message.reply_text('I expected a no. of hours.')
                else:
                    update.message.reply_text('I expected exactly one value.')
            else:
                update.message.reply_text('Snoozing is not available given '
                                          'that Redis is not set up.')

    def _unsnooze_callback(self, bot: Bot, update: Update):
        self._logger.info('Received /unsnooze command: bot=%s, update=%s',
                          bot, update)

        # If authorised, unsnooze phone calls if Redis enabled
        if self.cmd_handler.authorise(bot, update):
            if self._redis_enabled:
                # Remove snooze key if it exists
                if self._redis.exists(self._redis_snooze_key):
                    self._redis.remove(self._redis_snooze_key)
                    update.message.reply_text(
                        'Twilio calls have been unsnoozed.')
                else:
                    update.message.reply_text('Twilio calls were not snoozed.')
            else:
                update.message.reply_text('Unsnoozing is not available given '
                                          'that Redis is not set up.')

    def _status_callback(self, bot: Bot, update: Update):
        self._logger.info('Received /status command: bot=%s, update=%s',
                          bot, update)

        # If authorised, send status if Redis enabled
        if self.cmd_handler.authorise(bot, update):
            if self._redis_enabled:
                status = ""

                # Add Redis status
                # (step 1: check if it is running)
                try:
                    self._redis.ping_unsafe()
                    redis_running = True
                except (RedisError, ConnectionResetError):
                    redis_running = False
                except Exception as e:
                    self._logger.error('Unrecognized error when accessing '
                                       'Redis: %s', e)
                    redis_running = False

                # (step 2: add the actual status)
                if redis_running:
                    if self._redis.is_live:
                        status += '- Redis is running normally.\n'
                    else:
                        status += '- Redis is running normally but was not ' \
                                  'accessible a short while ago. Recent ' \
                                  'updates might be unavailable until the ' \
                                  'monitors detect Redis being active again.\n'
                else:
                    status += '- Redis is NOT accessible!\n'

                # Add Twilio calls snooze state to status
                if redis_running:
                    if self._redis.exists(self._redis_snooze_key):
                        until = self._redis.get(
                            self._redis_snooze_key).decode("utf-8")
                        status += '- Twilio calls are snoozed until {}.\n' \
                                  ''.format(until)
                    else:
                        status += '- Twilio calls are not snoozed.\n'

                # Add node monitor latest updates to status
                if redis_running:
                    node_monitor_keys_list = self._redis.get_keys(
                        self._redis_node_monitor_alive_key_prefix + '*')
                    node_monitor_names = [
                        k.replace(self._redis_node_monitor_alive_key_prefix, '')
                        for k in node_monitor_keys_list
                    ]
                    for key, name in zip(node_monitor_keys_list,
                                         node_monitor_names):
                        last_update = self._redis.get(key).decode('utf-8')
                        last_update = last_update.split('.')[
                            0]  # remove seconds
                        status += '- Last update from *{}*: `{}`.\n' \
                                  ''.format(name, last_update)

                    # Add note if no latest node monitor updates
                    if len(node_monitor_keys_list) == 0:
                        status += '- No recent update from node monitors.\n'

                # Add network monitor latest updates to status
                if redis_running:
                    net_monitor_keys_list = self._redis.get_keys(
                        self._redis_network_monitor_alive_key_prefix + '*')
                    net_monitor_names = [
                        k.replace(self._redis_network_monitor_alive_key_prefix,
                                  '')
                        for k in net_monitor_keys_list
                    ]
                    for key, name in zip(net_monitor_keys_list,
                                         net_monitor_names):
                        last_update = self._redis.get(key).decode('utf-8')
                        last_update = last_update.split('.')[
                            0]  # remove seconds
                        status += '- Last update from *{}*: `{}`.\n' \
                                  ''.format(name, last_update)

                    # Add note if no latest network monitor updates
                    if len(net_monitor_keys_list) == 0:
                        status += '- No recent update from network monitors.\n'

                # If redis is not running
                if not redis_running:
                    status += \
                        '- Since Redis is not accessible, Twilio calls are ' \
                        'considered not snoozed and any recent update from ' \
                        'node or network monitors is not accessible.\n'

                # Send status
                TelegramCommands.formatted_reply(
                    update, status[:-1] if status.endswith('\n') else status)
            else:
                update.message.reply_text('Status update not available given '
                                          'that Redis is not set up.')

    def _validators_callback(self, bot: Bot, update: Update):
        self._logger.info('Received /validators command: bot=%s, update=%s',
                          bot, update)

        # If authorised, send list of links to validators
        if self.cmd_handler.authorise(bot, update):
            update.message.reply_text(
                'Links to validators:\n'
                '  Hubble: {}\n'
                '  BigDipper: {}\n'
                '  Stargazer: {}\n'
                '  Mintscan: {}\n'
                '  Lunie: {}'.format(
                    self._internal_conf.validators_hubble_link,
                    self._internal_conf.validators_big_dipper_link,
                    self._internal_conf.validators_stargazer_link,
                    self._internal_conf.validators_mintscan_link,
                    self._internal_conf.validators_lunie_link))

    def _block_callback(self, bot: Bot, update: Update):
        self._logger.info('Received /block command: bot=%s, update=%s',
                          bot, update)

        # If authorised, send list of links to specified block
        if self.cmd_handler.authorise(bot, update):
            # Expected: /block <block>
            message_parts = update.message.text.split(' ')
            if len(message_parts) == 2:
                try:
                    block_height = int(message_parts[1])
                    update.message.reply_text(
                        'Links to block:\n'
                        '  Hubble: {}{}\n'
                        '  BigDipper: {}{}\n'
                        '  Stargazer: {}{}\n'
                        '  Mintscan: {}{}\n'
                        '  Lunie: {}{}'.format(
                            self._internal_conf.block_hubble_link_prefix,
                            block_height,
                            self._internal_conf.block_big_dipper_link_prefix,
                            block_height,
                            self._internal_conf.block_stargazer_link_prefix,
                            block_height,
                            self._internal_conf.block_mintscan_link_prefix,
                            block_height,
                            self._internal_conf.block_lunie_link_prefix,
                            block_height))
                except ValueError:
                    update.message.reply_text("I expected a block height.")
            else:
                update.message.reply_text("I expected exactly one value.")

    def _tx_callback(self, bot: Bot, update: Update):
        self._logger.info('Received /tx command: bot=%s, update=%s',
                          bot, update)

        # If authorised, send list of links to specified transaction
        if self.cmd_handler.authorise(bot, update):
            # Expected: /tx <tx-hash>
            message_parts = update.message.text.split(' ')
            if len(message_parts) == 2:
                tx_hash = message_parts[1]
                update.message.reply_text(
                    'Links to transaction:\n'
                    '  Hubble: {}\n'
                    '  BigDipper: {}\n'
                    '  Mintscan: {}'.format(
                        self._internal_conf.tx_hubble_link_prefix +
                        str(tx_hash),
                        self._internal_conf.tx_big_dipper_link_prefix +
                        str(tx_hash),
                        self._internal_conf.tx_mintscan_link_prefix +
                        str(tx_hash)))
            else:
                update.message.reply_text("I expected exactly one value.")

    def _help_callback(self, bot: Bot, update: Update):
        self._logger.info('Received /help command: bot=%s, update=%s',
                          bot, update)

        # If authorised, send help message with available commands
        if self.cmd_handler.authorise(bot, update):
            update.message.reply_text(
                'Hey! These are the available commands:\n'
                '  /start: welcome message\n'
                '  /ping: ping the Telegram commands handler\n'
                '  /snooze <hours>: snoozes phone calls for <hours>\n'
                '  /unsnooze: unsnoozes phone calls\n'
                '  /status: shows status message\n'
                '  /validators: shows links to validators\n'
                '  /block <height>: shows link to specified block\n'
                '  /tx <tx-hash>: shows link to specified transaction\n'
                '  /help: shows this message')

    def _unknown_callback(self, bot: Bot, update: Update) -> None:
        self._logger.info('Received unrecognized command: bot=%s, update=%s',
                          bot, update)

        # If authorised, send a default message for unrecognized commands
        if self.cmd_handler.authorise(bot, update):
            update.message.reply_text('I did not understand (Type /help)')