Exemple #1
0
def get_number_users_by_feature(feature: str, feature_type: str) -> int:
    """
    Get number of users that have same smartphone, camera, lens or that
    have been to the same country
    :param feature: string which is name of a particular feature e.g.
    camera name or country name
    :param feature_type: string which is name of the column in database
    :return: string which is a message to user
    """
    log.debug('Check how many users also have this feature: %s...',
              feature)

    query = ("SELECT DISTINCT chat_id "
             "FROM photo_queries_table2 "
             f"WHERE {feature_type}=%s")

    parameters = feature,

    try:
        cursor = db.execute_query(query, parameters)
    except DatabaseConnectionError:
        log.error("Cannot check how many users also have this feature: %s...",
                  feature)
        raise

    if not cursor.rowcount:
        log.debug('There were no users with %s...', feature)
        return 0

    log.debug('There is %d users with %s', cursor.rowcount, feature)
    return cursor.rowcount - 1
Exemple #2
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")
Exemple #3
0
    def get_total_number() -> int:
        """
        Count the total number of users in the database

        :return: integer which is the total number of users
        """
        query = "SELECT COUNT(*) FROM users"
        try:
            cursor = db.execute_query(query)
        except DatabaseConnectionError:
            log.error("Can't count the total number of users!")
            raise

        return cursor.fetchone()[0]
Exemple #4
0
    def add(self, query: str, parameters: tuple = None):
        """
        Shortcut to add something to a database

        :param query: query to execute
        :param parameters: parameters for query
        :return: boolean - True if the method succeeded and False otherwise
        """

        try:
            self.execute_query(query, parameters)
            self.conn.commit()
        except Exception as e:
            log.error(e)
            raise DatabaseError("Cannot add your data to the database!")
Exemple #5
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)
Exemple #6
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")
Exemple #7
0
    def start_bot(self) -> None:
        """
        Method to get the bot started

        Wrapper around self._run just for the sake of making it more reliable
        and reconnect in case of errors. And to store time when bot started to
        work

        :return: None
        """
        try:
            self.start_time = datetime.now()
            self._run()

        except requests.exceptions.ReadTimeout as e:
            log.error(e)
            self.stop_polling()
            log.warning('Pausing bot for 30 seconds...')
            time.sleep(30)
            log.warning('Try to start the bot again...')
            self._run()
Exemple #8
0
    def set_language(self, lang: str) -> None:
        """
        Update language of user in the User object and in the database

        :param lang: string with language tag like "en-US"
        :return: None
        """
        log.debug(
            'Updating info about user %s language '
            'in memory & database...', self)

        self.language = lang

        query = ("UPDATE users " f"SET language=%s " f"WHERE chat_id=%s")

        parameters = self.language, self.chat_id
        try:
            db.add(query, parameters)
        except DatabaseError:
            log.error("Can't add new language of %s to the database", self)
        else:
            log.debug('Language updated.')
Exemple #9
0
def admin_menu(call: CallbackQuery) -> None:
    """
    Respond to commands from the admin menu

    :param call: object that contains info about user's reaction to an
    interactive keyboard

    :return: None
    """

    # Remove progress bar from pressed button
    bot.answer_callback_query(callback_query_id=call.id, show_alert=False)

    if call.data == 'off':
        if db.disconnect():
            bot.turn_off()
        else:
            log.error('Cannot stop bot.')
            bot.send_message(chat_id=config.MY_TELEGRAM,
                             text='Cannot stop bot.')
    elif call.data == 'last active':
        bot.send_message(config.MY_TELEGRAM,
                         text=get_admin_stat('last active users'))
    elif call.data == 'total number photos sent':
        bot.send_message(config.MY_TELEGRAM,
                         text=get_admin_stat('total number photos sent'))
    elif call.data == 'photos today':
        bot.send_message(config.MY_TELEGRAM,
                         text=get_admin_stat('photos today'))
    elif call.data == 'number of users':
        bot.send_message(config.MY_TELEGRAM,
                         text=get_admin_stat('number of users'))
    elif call.data == 'number of gadgets':
        bot.send_message(config.MY_TELEGRAM,
                         text=get_admin_stat('number of gadgets'))
    elif call.data == 'uptime':
        bot.send_message(config.MY_TELEGRAM,
                         text=get_admin_stat('uptime'))
Exemple #10
0
    def _get_address(self, latitude: float, longitude: float) \
            -> Tuple[Dict[str, str], Dict[str, str]]:
        """
         # Get address as a string by coordinates from photo that user sent
         to bot

        :param latitude: latitude from a photo as a float
        :param longitude: longitude rom a photo as a float
        :return: address as a string where photo was taken; name of
        country in English and Russian to keep statistics
        of the most popular countries among users of the bot
        """

        address = {}
        country = {}
        coordinates = f"{latitude}, {longitude}"
        log.debug('Getting address from coordinates %s...', coordinates)
        geolocator = Nominatim()
        lang = self.user.language

        try:
            # Get name of the country in English and Russian language
            location_en = geolocator.reverse(coordinates, language='en')
            address['en-US'] = location_en.address
            country['en-US'] = location_en.raw['address']['country']

            location_ru = geolocator.reverse(coordinates, language='ru')
            address['ru-RU'] = location_ru.address
            country['ru-RU'] = location_ru.raw['address']['country']

            address = address[lang]
            return address, country

        except Exception as e:
            log.error(e)
            log.error('Getting address has failed!')
            raise
Exemple #11
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.')
Exemple #12
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
Exemple #13
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
Exemple #14
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
Exemple #15
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
Exemple #16
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])