Example #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]
Example #2
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)
Example #3
0
    def _open_ssh_tunnel(self) -> None:
        """
        Method that opens a new ssh tunnel to the server where the database of
        photogpsbot is located

        :return: None
        """
        log.debug('Establishing SSH tunnel to the server where the database '
                  'is located...')
        sshtunnel.SSH_TIMEOUT = 5.0
        sshtunnel.TUNNEL_TIMEOUT = 5.0
        self.tunnel = sshtunnel.SSHTunnelForwarder(
            ssh_address_or_host=config.SERVER_ADDRESS,
            ssh_username=config.SSH_USER,
            ssh_password=config.SSH_PASSWD,
            ssh_port=22,
            remote_bind_address=('127.0.0.1', 3306))

        self.tunnel.start()
        self.tunnel_opened = True
        log.debug('SSH tunnel has been established.')
Example #4
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
Example #5
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.')
Example #6
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
Example #7
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.')
Example #8
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")
Example #9
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
Example #10
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])