コード例 #1
0
    def func_launcher(*args, **kwargs) -> Any:
        nonlocal when_was_called
        item_type = kwargs.get('item_type', None)
        feature = kwargs.get('feature', None)
        message = kwargs.get('message', None)

        if item_type == 'country_ru' or item_type == 'country_en':
            result_id = users.find_one(message).language + item_type
        elif item_type or feature:
            result_id = item_type or feature
        else:
            raise ValueError("Cannot find a key to cache your function")

        high_time = (when_was_called + timedelta(minutes=CACHE_TIME) <
                     datetime.now() if when_was_called else True)

        if not cache.get(result_id, None) or not when_was_called or high_time:
            when_was_called = datetime.now()
            result = func(*args, **kwargs)
            cache[result_id] = result
            return result
        else:
            log.info('Returning cached result of %s', func.__name__)
            time_left = (when_was_called + timedelta(minutes=CACHE_TIME) -
                         datetime.now())
            log.debug('Time to to reevaluate result of %s is %s',
                      func.__name__, str(time_left)[:-7])
            return cache[result_id]
コード例 #2
0
    def _run(self) -> None:
        """
        Make bot start polling

        :return: None
        """
        log.info('Starting photogpsbot...')
        # Keep bot receiving messages
        self.polling(none_stop=True, timeout=90)
コード例 #3
0
    def compare_and_update(user, message: Message) -> None:
        """
        Updates user's info if needed

        This method compare a user object from the bot and his info from
        the Telegram message to check whether a user has changed his bio
        or not. If yes, the user object that represents him in the bot will
        be updated accordingly. Now this function is called only when a user
        asks the bot for showing the most popular cams

        :param user: user object that represents a Telegram user in this bot
        :param message: object from Telegram that contains info about user's
        message and about himself
        :return: None
        """

        log.info('Checking whether user have changed his info or not...')
        msg = message.from_user
        usr_from_message = User(message.chat.id, msg.first_name, msg.username,
                                msg.last_name)

        if user.chat_id != usr_from_message.chat_id:
            log.error("Wrong user to compare!")
            return

        if user.first_name != usr_from_message.first_name:
            user.first_name = usr_from_message.first_name

        elif user.nickname != usr_from_message.nickname:
            user.nickname = usr_from_message.nickname

        elif user.last_name != usr_from_message.last_name:
            user.last_name = usr_from_message.last_name

        else:
            log.debug("User's info hasn't changed")
            return

        log.info("User has changed his info")
        log.debug("Updating user's info in the database...")
        query = (f"UPDATE users "
                 f"SET first_name=%s, "
                 f"nickname=%s, "
                 f"last_name=%s "
                 f"WHERE chat_id=%s")

        parameters = (user.first_name, user.nickname, user.last_name,
                      user.chat_id)

        try:
            db.add(query, parameters)
        except DatabaseError:
            log.error("Could not update info about %s in the database", user)
        else:
            log.debug("User's info has been updated")
コード例 #4
0
def answer_photo_message(message: Message) -> None:
    """
    Handles situations when user sends a photo as a photo not as a file,
    namely answers to him that he need to send his photo as a file

    :param message: Message objects from Telebot that contains all the data
    about user's message
    :return: none
    """
    user = users.find_one(message)
    bot.send_message(user.chat_id, messages[user.language]['as_file'])
    log.info('%s sent photo as a photo.', user)
コード例 #5
0
    def turn_off(self) -> None:
        """
        Safely turn the bot off and message to its admin

        :return: None
        """

        self.send_message(chat_id=config.MY_TELEGRAM, text='bye')
        log.info('Please wait for a sec, bot is turning off...')
        self.stop_polling()
        log.info('Auf Wiedersehen! Bot is turned off.')
        sys.exit()
コード例 #6
0
    def disconnect(self) -> bool:
        """
        Closes the connection to the database and ssh tunnel if needed

        :return: True if succeeded
        """
        if self.conn:
            self.conn.close()
            log.info('Connection to the database has been closed.')
        if self.tunnel:
            self.tunnel.stop()
            log.info('SSH tunnel has been closed.')
        self.tunnel_opened = False
        return True
コード例 #7
0
    def execute_query(self,
                      query: str,
                      parameters: tuple = None,
                      trials: int = 0):
        """
        Executes a given query

        :param query: query to execute
        :param parameters: parameters for query
        :param trials: integer that denotes number of trials to execute
        a query in case of known errors
        :return: cursor object
        """
        if not self.conn or not self.conn.open:
            self.connect()

        try:
            cursor = self.conn.cursor()
            cursor.execute(query, parameters)

        # try to reconnect if MySQL server has gone away
        except MySQLdb.OperationalError as e:

            # (2013, Lost connection to MySQL server during query)
            # (2006, Server has gone away)
            if e.args[0] in [2006, 2013]:
                log.info(e)

                self.connect()
                if trials <= 3:
                    # trying to execute query one more time
                    trials += 1
                    log.warning(e)
                    log.info("Trying execute the query again...")
                    return self.execute_query(query, parameters, trials)

                if trials > 3:
                    log.error(e)
                    log.warning("Ran out of limit of trials...")
                    raise DatabaseConnectionError("Cannot connect to the "
                                                  "database")
            else:
                log.error(e)
                raise
        except Exception as e:
            log.error(e)
            raise
        else:
            return cursor
コード例 #8
0
    def switch_language(self) -> str:
        """
        Switch language from Russian to English or conversely

        :return: string with language tag like "en-US" to be used for
        rendering menus and messages for user
        """
        curr_lang = self.language
        new_lang = 'ru-RU' if self.language == 'en-US' else 'en-US'
        log.info('Changing user %s language from %s to %s...', self, curr_lang,
                 new_lang)

        self.set_language(new_lang)

        return new_lang
コード例 #9
0
    def clean_cache(self, limit: int) -> None:
        """
        Method that remove several User objects from cache - the least 
        active users

        :param limit: number of the users that the method should remove
        from cache
        :return: None
        """

        log.info('Figuring out the least active users...')
        # Select users that the least active recently
        user_ids = tuple(self.users.keys())
        query = ('SELECT chat_id '
                 'FROM photo_queries_table2 '
                 f'WHERE chat_id in {user_ids} '
                 'GROUP BY chat_id '
                 'ORDER BY MAX(time) '
                 f'LIMIT %s')

        parameters = limit,

        try:
            cursor = db.execute_query(query, parameters)
        except DatabaseConnectionError:
            log.error("Can't figure out the least active users...")
            return

        if not cursor.rowcount:
            log.warning("There are no users in the db")
            return

        # Make list out of tuple of tuples that is returned by MySQL
        least_active_users = [chat_id[0] for chat_id in cursor.fetchall()]
        log.info('Removing %d least active users from cache...', limit)
        num_deleted_entries = 0
        for entry in least_active_users:
            log.debug('Deleting %s...', entry)
            deleted_entry = self.users.pop(entry, None)
            if deleted_entry:
                num_deleted_entries += 1
        log.debug("%d users were removed from cache.", num_deleted_entries)
コード例 #10
0
    def _add_to_db(user: User) -> None:
        """
        Adds the User object to the database

        :param user: User object with info about user
        :return: None
        """
        query = ("INSERT INTO users (chat_id, first_name, nickname, "
                 "last_name, language) "
                 f"VALUES (%s, %s, %s, %s, %s)")

        parameters = (user.chat_id, user.first_name, user.nickname,
                      user.last_name, user.language)

        try:
            db.add(query, parameters)
        except DatabaseError:
            log.error("Cannot add user to the database")
        else:
            log.info(f"User {user} was successfully added to the users db")
コード例 #11
0
def handle_message_with_image(message: Message) -> None:

    user = users.find_one(message)
    # Sending a message to a user that his photo is being processed
    bot.reply_to(message, messages[user.language]['photo_prcs'])
    log.info('%s sent photo as a file.', user)

    photo_message = PhotoMessage(message, user)
    answer = photo_message.prepare_answer()

    # if longitude is in the answer
    if answer.coordinates:
        # extract longitude and latitude
        bot.send_location(user.chat_id,
                          answer.coordinates[0],
                          answer.coordinates[1],
                          live_period=None)
        bot.reply_to(message, answer.answer, parse_mode='Markdown')
    else:
        bot.reply_to(message, answer.answer, parse_mode='Markdown')
コード例 #12
0
    def connect(self) -> None:
        """
        Connects the bot to a database

        Established connection either to a local database or to a remote one if
        the script runs not on the same server where database is located

        :return: None
        """
        if socket.gethostname() == config.PROD_HOST_NAME:
            log.info('Connecting to the local database...')
            port = 3306
        else:
            log.info('Connecting to the database via SSH...')
            if not self.tunnel_opened:
                self._open_ssh_tunnel()

            port = self.tunnel.local_bind_port

        self.conn = MySQLdb.connect(host='127.0.0.1',
                                    user=config.DB_USER,
                                    password=config.DB_PASSWD,
                                    port=port,
                                    database=config.DB_NAME,
                                    charset='utf8')
        log.info('Connected to the database.')
コード例 #13
0
    def _convert_coordinates(self,
                             raw_data: RawImageData) -> Tuple[float, float]:
        """
        Converts coordinates to the common format

        Convert GPS coordinates from format in which they are stored in
        EXIF of photo to format that accepts Telegram (and Google Maps for
        example)

        :param raw_data: info about an image with its coordinates
        :return: tuple of floats that represents longitude and latitude
        """

        try:
            latitude = self._get_dd_coordinate(raw_data.raw_latitude,
                                               raw_data.latitude_reference)
            longitude = self._get_dd_coordinate(raw_data.raw_longitude,
                                                raw_data.longitude_reference)

        except (AttributeError, TypeError) as e:
            log.info(e)
            log.info("The photo does not contain proper coordinates")
            raise NoCoordinates

        except Exception as e:
            log.info(e)
            log.info('Cannot read coordinates of this photo.')
            raw_coordinates = (f'Latitude reference: '
                               f'{raw_data.latitude_reference}\n'
                               f'Raw latitude: {raw_data.raw_latitude}.\n'
                               f'Longitude reference: '
                               f'{raw_data.longitude_reference}\n'
                               f'Raw longitude: {raw_data.raw_longitude}.\n')
            log.info(raw_coordinates)
            raise InvalidCoordinates

        return latitude, longitude
コード例 #14
0
    def _check_camera_tags(*tags: str) -> List[str]:
        """
        Converts camera and lens name to proper ones

        Function that convert stupid code name of a smartphone or a camera
        from EXIF to a meaningful one by looking a collation in a special MySQL
        table For example instead of just Nikon there can be
        NIKON CORPORATION in EXIF

        :param tags: a tuple with a name of a camera and lens from EXIF
        :return: list with one or two strings which are name of
        camera and/or lens. If there is not better name for the gadget
        in database, function just returns name how it is
        """
        checked_tags = []

        for tag in tags:
            if tag:  # If there was this information inside EXIF of the photo
                tag = str(tag).strip()
                log.info('Looking up collation for %s', tag)
                query = ('SELECT right_tag '
                         'FROM tag_table '
                         'WHERE wrong_tag=%s')
                parameters = tag,
                cursor = db.execute_query(query, parameters)
                if not cursor:
                    log.error("Can't check the tag because of the db error")
                    log.warning("Tag will stay as is.")
                    continue
                if cursor.rowcount:
                    # Get appropriate tag from the table
                    tag = cursor.fetchone()[0]
                    log.info('Tag after looking up in tag_tables - %s.', tag)

            checked_tags.append(tag)
        return checked_tags
コード例 #15
0
    def save_info_to_db(self, image_data: ImageData) -> None:
        """
        Insert info about user's query to the database

        When user sends photo as a file to get information, bot also stores
        information about this query to the database to keep statistics that
        can be shown to a user in different ways. It stores time of query,
        Telegram id of a user, his camera and lens which were used for
        taking photo, his first and last name, nickname and country where
        the photo was taken. The bot does not save photos or their
        coordinates.

        :image_data: an instance of ImageData dataclass with info about
        the image
        :return: None
        """
        camera_name, lens_name = image_data.camera, image_data.lens

        if not image_data.country:
            country_en = country_ru = None
        else:
            country_en = image_data.country["en-US"]
            country_ru = image_data.country["ru-RU"]

        log.info('Adding user query to photo_queries_table...')

        query = ('INSERT INTO photo_queries_table2 '
                 '(chat_id, camera_name, lens_name, country_en, country_ru) '
                 'VALUES (%s, %s, %s, %s, %s)')

        parameters = (self.user.chat_id, camera_name, lens_name, country_en,
                      country_ru)

        db.execute_query(query, parameters)
        db.conn.commit()
        log.info('User query was successfully added to the database.')
コード例 #16
0
    def cache(self, limit: int) -> None:
        """
        Caches last active users from database to a dictionary inside object of
        this class

        :param limit: limit of entries to be cached
        :return: None
        """

        log.debug("Start caching last active users from the DB...")

        try:
            last_active_users = self.get_last_active_users(limit)
        except DatabaseConnectionError:
            log.error("Cannot cache users!")
            return

        for items in last_active_users:
            # if chat_id of a user is not known to the bot
            if items[0] not in self.users:
                # adding a user from the database to the "cache"
                self.users[items[0]] = User(*items)
                log.debug("Caching user: %s", self.users[items[0]])
        log.info('Users have been cached.')
コード例 #17
0
    def get_last_active_users(
            limit: int) -> Tuple[Tuple[int, str, str, str, str]]:
        """
        Get from the database a tuple of users who have been recently using
        the bot

        :param limit: integer that specifies how much users to get
        :return: tuple of tuples with user's info
        """
        log.info('Evaluating last active users with date of '
                 'last time when they used bot...')

        # From photo_queries_table2 we take chat_id of the last
        # active users and from 'users' table we take info about these
        # users by chat_id which is a foreign key
        query = ('SELECT p.chat_id, u.first_name, u.nickname, u.last_name, '
                 'u.language '
                 'FROM photo_queries_table2 p '
                 'INNER JOIN users u '
                 'ON p.chat_id = u.chat_id '
                 'GROUP BY u.chat_id, u.first_name, u.nickname, u.last_name, '
                 'u.language '
                 'ORDER BY MAX(time)'
                 f'DESC LIMIT %s')

        parameters = limit,

        try:
            cursor = db.execute_query(query, parameters)
        except DatabaseConnectionError:
            log.error("Cannot get the last active users because of some "
                      "problems with the database")
            raise

        last_active_users = cursor.fetchall()
        return last_active_users
コード例 #18
0
    def _get_raw_data(self, file: BytesIO) -> RawImageData:
        """
        Gets raw information out of an image

        Get name of the camera and lens, the date when the photo was taken
        and raw coordinates (which later will be converted)
        :param file: byte sting with an image
        :return: RawImageData object with raw info from the photo
        """
        # Get data from the exif of the photo via external library
        exif = exifread.process_file(file, details=False)
        if not len(exif.keys()):
            reason = "This picture doesn't contain EXIF."
            log.info(reason)
            raise NoEXIF(reason)

        # Get info about camera ang lend from EXIF
        date_time = exif.get('EXIF DateTimeOriginal', None)
        date_time = str(date_time) if date_time else None
        camera_brand = str(exif.get('Image Make', ''))
        camera_model = str(exif.get('Image Model', ''))
        lens_brand = str(exif.get('EXIF LensMake', ''))
        lens_model = str(exif.get('EXIF LensModel', ''))

        if not any(
            [date_time, camera_brand, camera_model, lens_brand, lens_model]):
            # Means that there is actually no any data of our interest
            reason = 'There is no data of interest in this photo'
            log.info(reason)
            raise NoData(reason)

        try:  # Extract coordinates from EXIF
            lat_ref = exif['GPS GPSLatitudeRef']
            lon_ref = exif['GPS GPSLongitudeRef']

            # prevent having ifdtag instead of a plane None
            latitude_reference = str(lat_ref) if lat_ref.values else None
            longitude_reference = str(lon_ref) if lon_ref.values else None

            raw_latitude = exif['GPS GPSLatitude']
            raw_longitude = exif['GPS GPSLongitude']

        except (KeyError, AttributeError):
            log.info("This picture doesn't contain coordinates.")
            # returning info about the photo without coordinates
            return RawImageData(self.user, date_time, camera_brand,
                                camera_model, lens_brand, lens_model)
        else:
            # returning info about the photo with its coordinates
            return RawImageData(self.user, date_time, camera_brand,
                                camera_model, lens_brand, lens_model,
                                latitude_reference, raw_latitude,
                                longitude_reference, raw_longitude)
コード例 #19
0
def handle_menu_response(message: Message) -> None:
    """
    Function that handles user's respond to the main keyboard

    :param message: user's message
    :return: None
    """

    # keyboard_hider = telebot.types.ReplyKeyboardRemove()
    current_user_lang = users.find_one(message).language
    user = users.find_one(message)

    if message.text == 'Русский/English':

        new_lang = users.find_one(message).switch_language()
        if current_user_lang != new_lang:
            bot.send_message(user.chat_id, messages[new_lang]
                             ['switch_lang_success'])
            create_main_keyboard(message)
        else:
            bot.send_message(user.chat_id, messages[new_lang]
                             ['switch_lang_failure'])
            create_main_keyboard(message)

    elif message.text == messages[current_user_lang]['top_cams']:
        log.info('User %s asked for top cams', user)
        bot.send_message(user.chat_id,
                         text=get_most_popular_items(item_type='camera_name',
                                                     message=message))
        log.info('List of most popular cameras '
                 'has been returned to %s', user)

        # in order not to check whether user has changed his nickname or
        # whatever every time his sends any request the bot will just check
        # it every time a user wants to get a statistic about the most
        # popular cameras
        users.compare_and_update(user, message)

    elif message.text == messages[current_user_lang]['top_lens']:
        log.info('User %s asked for top lens', user)
        bot.send_message(user.chat_id,
                         text=get_most_popular_items(item_type='lens_name',
                                                     message=message))
        log.info('List of most popular lens has been returned to %s', user)

    elif message.text == messages[current_user_lang]['top_countries']:
        log.info('User %s asked for top countries', user)
        lang_table_name = ('country_ru'
                           if current_user_lang == 'ru-RU' else 'country_en')
        bot.send_message(user.chat_id,
                         text=get_most_popular_items(item_type=lang_table_name,
                                                     message=message))
        log.info('List of most popular countries has '
                 'been returned to %s', user)

    elif (message.text.lower() == 'admin' and
          user.chat_id == int(config.MY_TELEGRAM)):
        # Creates inline keyboard with options for admin Function that handle
        # user interaction with the keyboard called admin_menu

        keyboard = types.InlineKeyboardMarkup()  # Make keyboard object
        button = types.InlineKeyboardButton  # just an alias to save space

        keyboard.add(button(text='Turn bot off', callback_data='off'))
        keyboard.add(button(text='Last active users',
                            callback_data='last active'))
        keyboard.add(button(text='Total number of photos were sent',
                            callback_data='total number photos sent'))
        keyboard.add(button(text='Number of photos today',
                            callback_data='photos today'))
        keyboard.add(button(text='Number of users',
                            callback_data='number of users'))
        keyboard.add(button(text='Number of gadgets',
                            callback_data='number of gadgets'))
        keyboard.add(button(text='Uptime', callback_data='uptime'))
        bot.send_message(config.MY_TELEGRAM,
                         'Admin commands', reply_markup=keyboard)

    else:
        log.info('%s sent text message.', user)

        # Answer to user that bot can't make a conversation with him
        bot.send_message(user.chat_id,
                         messages[current_user_lang]['dont_speak'])
コード例 #20
0
def get_most_popular_items(item_type: str, message: Message) -> str:
    """
    Get the most common cameras/lenses/countries from database and
    make list of them

    :param item_type: string with column name to choose between cameras,
    lenses and countries
    :param message: telebot object with info about user and his message
    :return: string which is either list of most common
    cameras/lenses/countries or message which states that list is
    empty
    """

    user = users.find_one(message)

    def tuple_to_ordered_str_list(list_of_gadgets: Tuple[Tuple[str]]) -> str:
        """
        Converts Python list to ordered list as a string

        Example:
        1. Canon 80D
        2. iPhone 4S

        :param list_of_gadgets: tuple of tuples with string where every string
        is a name of a camera or lens or a country
        :return: ordered list as a string
        """

        string_roaster = ''
        index = 1
        for item in list_of_gadgets:
            if not item[0]:
                continue
            string_roaster += '{}. {}\n'.format(index, item[0])
            index += 1
        return string_roaster

    log.debug('Evaluating most popular things...')

    # This query returns item types in order where the first one item
    # has the highest number of occurrences
    # in a given column

    query = (f'SELECT {item_type} FROM photo_queries_table2 '
             f'GROUP BY {item_type} '
             f'ORDER BY count({item_type}) '
             'DESC')

    try:
        cursor = db.execute_query(query)
    except DatabaseConnectionError:
        log.error("Can't evaluate a list of the most popular items")
        return messages[user.language]['doesnt work']

    # Almost impossible case but still
    if not cursor.rowcount:
        log.warning('There is nothing in the main database table')
        bot.send_message(chat_id=config.MY_TELEGRAM,
                         text='There is nothing in the main database table')
        return messages[user.language]['no_top']

    popular_items = cursor.fetchall()
    log.info('Finish evaluating the most popular items')
    return tuple_to_ordered_str_list(popular_items[:30])
コード例 #21
0
    def find_one(self, message: Message) -> User:
        """
        Look up a user by a message which we get together with request
        from Telegram

        :param message: object from Telegram that contains info about user's
        message and about himself
        :return: user object that represents a Telegram user in this bot
        """

        # look up user in the cache of the bot
        user = self.users.get(message.chat.id, None)

        if user:
            return user

        # otherwise look up the user in the database
        log.debug("Looking up the user in the database as it doesn't "
                  "appear in cache")
        query = (f'SELECT first_name, nickname, last_name, language '
                 f'FROM users '
                 f'WHERE chat_id=%s')

        parameters = message.chat.id,
        try:
            cursor = db.execute_query(query, parameters)
        except DatabaseConnectionError:

            # Even if the database in unreachable add user to dictionary
            # with users otherwise the bot will crash requesting this
            # user's info
            log.error('Cannot lookup the user with chat_id %d in database',
                      message.chat.id)
            msg = message.from_user
            user = self.add_new_one(message.chat.id,
                                    msg.first_name,
                                    msg.last_name,
                                    msg.username,
                                    language='en-US',
                                    add_to_db=False)
            return user

        if not cursor.rowcount:
            # This user uses our photoGPSbot for the first time as we
            # can't find him in the database
            log.info('Adding totally new user to the system...')
            msg = message.from_user
            user = self.add_new_one(message.chat.id,
                                    msg.first_name,
                                    msg.last_name,
                                    msg.username,
                                    language='en-US')
            bot.send_message(config.MY_TELEGRAM,
                             text=f'You have a new user! {user}')
            log.info('You have a new user! Welcome %s', user)

        # finally if the user wasn't found in the cache of the bot, but was
        # found in the database
        else:
            log.debug('User %d has been found in the database',
                      message.chat.id)

            user_data = cursor.fetchall()[0]
            user = self.add_new_one(message.chat.id,
                                    *user_data,
                                    add_to_db=False)

        return user
コード例 #22
0
def get_admin_stat(command: str) -> str:
    """
    Function that returns statistics to admin by command

    :param command: string with a command what kind of statistics to prepare
    :return: a string with either answer with statistics or an error message
    """
    error_answer = "Can't execute your command. Check logs"
    answer = 'There is some statistics for you: \n'

    # Set to a beginning of the day
    today = (datetime
             .today()
             .replace(hour=0, minute=0, second=0, microsecond=0)
             .strftime('%Y-%m-%d %H:%M:%S'))

    # Last users with date of last time when they used bot
    if command == 'last active users':
        try:
            last_active_users = users.get_last_active_users(100)
        except DatabaseConnectionError:
            return error_answer

        bot_users = ''
        # Makes a human readable list of last active users
        for usr, index in zip(last_active_users,
                              range(len(last_active_users))):
            user = User(*usr)
            bot_users += f'{index + 1}. {user}\n'

        answer = ('Up to 100 last active users by the time when they sent '
                  'picture last time:\n')
        answer += bot_users
        log.info('Done.')
        return answer

    elif command == 'total number photos sent':
        log.info('Evaluating total number of photo queries in database...')
        query = ('SELECT COUNT(chat_id) '
                 'FROM photo_queries_table2')
        try:
            cursor = db.execute_query(query)
        except DatabaseConnectionError:
            return error_answer
        answer += f'{cursor.fetchone()[0]} times users sent photos.'
        query = ('SELECT COUNT(chat_id) '
                 'FROM photo_queries_table2 '
                 'WHERE chat_id !=%s')
        parameters = config.MY_TELEGRAM,
        try:
            cursor = db.execute_query(query, parameters)
        except DatabaseConnectionError:
            answer += ("\nCannot calculate number of photos that were send "
                       "excluding your photos. Check logs")
            return answer

        answer += f'\nExcept you: {cursor.fetchone()[0]} times.'
        log.info('Done.')
        return answer

    elif command == 'photos today':
        # Show how many photos have been sent since 00:00:00 of today
        log.info('Evaluating number of photos which were sent today.')
        query = ("SELECT COUNT(chat_id) "
                 "FROM photo_queries_table2 "
                 "WHERE time > %s")

        parameters = today,
        try:
            cursor = db.execute_query(query, parameters)
        except DatabaseConnectionError:
            return error_answer
        answer += f'{cursor.fetchone()[0]} times users sent photos today.'
        query = ("SELECT COUNT(chat_id) "
                 "FROM photo_queries_table2 "
                 "WHERE time > %s "
                 "AND chat_id !=%s")

        parameters = today, config.MY_TELEGRAM
        try:
            cursor = db.execute_query(query, parameters)
        except DatabaseConnectionError:
            return error_answer

        answer += '\nExcept you: {} times.'.format(cursor.fetchone()[0])
        log.info('Done.')
        return answer

    elif command == 'number of users':
        # Show number of users who has used bot at leas"
        # once or more (first for the whole time, then today)
        log.info('Evaluating number of users that use bot '
                 'since the first day and today...')
        try:
            num_of_users = users.get_total_number()
        except DatabaseConnectionError:
            return error_answer

        answer += f'There are totally {num_of_users} users.'

        query = ("SELECT COUNT(DISTINCT chat_id) "
                 "FROM photo_queries_table2 "
                 "WHERE time > %s")
        parameters = today,
        try:
            cursor = db.execute_query(query, parameters)
        except DatabaseConnectionError:
            answer += ("\nCannot calculate how many user have sent their "
                       "photos today")
            return answer

        answer += f'\n{cursor.fetchone()[0]} users have sent photos today.'
        log.info('Done.')
        return answer

    elif command == 'number of gadgets':
        # To show you number smartphones + cameras in database
        log.info('Evaluating number of cameras and smartphones in database...')
        query = ('SELECT COUNT(DISTINCT camera_name) '
                 'FROM photo_queries_table2')
        try:
            cursor = db.execute_query(query)
        except DatabaseConnectionError:
            return error_answer
        answer += (f'There are totally {cursor.fetchone()[0]} '
                   f'cameras/smartphones.')
        query = ("SELECT COUNT(DISTINCT camera_name) "
                 "FROM photo_queries_table2 "
                 "WHERE time > %s")
        parameters = today,
        try:
            cursor = db.execute_query(query, parameters)
        except DatabaseConnectionError:
            answer += ("Cannot calculate the number of gadgets that have been "
                       "used today so far")
            return answer

        answer += (f'\n{cursor.fetchone()[0]} cameras/smartphones '
                   'were used today.')
        log.info('Done.')
        return answer

    elif command == 'uptime':
        fmt = 'Uptime: {} days, {} hours, {} minutes and {} seconds.'
        td = datetime.now() - bot.start_time
        # datetime.timedelta.seconds returns you total number of seconds
        # since given time, so you need to perform
        # a little bit of math to make whole hours, minutes and seconds from it
        # And there isn't any normal way to do it in Python unfortunately
        uptime = fmt.format(td.days, td.seconds // 3600, td.seconds % 3600 //
                            60, td.seconds % 60)
        log.info(uptime)
        return uptime
    else:
        return 'There is no such a command'