Пример #1
0
class V(Vk):
    def __init__(self, token, group_id, debug=True):
        Vk.__init__(self, token=token, group_id=group_id, debug=debug)
        self.messages_to_delete = {}
        self.userbot = UserBot()
        self.debug = True

        base = BetterBotBase("users", "dat")
        base.addPattern("karma", 0)
        base.addPattern("programming_languages", [])
        base.addPattern("github_profile", "")
        base.addPattern("supporters", [])
        base.addPattern("opponents", [])
        base.addPattern("last_collective_vote", 0)

        self.base = base

    def message_new(self, event):
        """
        Handling all new messages.
        """
        event = event["object"]["message"]

        if event['peer_id'] in self.messages_to_delete:
            peer = CHAT_ID_OFFSET + config.userbot_chats[event['peer_id']]
            new_messages_to_delete = []
            ids = []

            for item in self.messages_to_delete[event['peer_id']]:
                if item['date'] > datetime.now():
                    new_messages_to_delete.append(item)
                else:
                    ids.append(item['id'])

            if new_messages_to_delete:
                self.messages_to_delete[event['peer_id']] = new_messages_to_delete
            else:
                self.messages_to_delete.pop(event['peer_id'])

            if ids:
                self.userbot.delete_messages(ids, peer)

        user = self.base.autoInstall(event["from_id"], self) if event["from_id"] > 0 else None

        message = event["text"].lstrip("/")
        messages = self.get_messages(event)
        selected_message = messages[0] if len(messages) == 1 else None
        selected_user = self.base.autoInstall(selected_message["from_id"], self) if selected_message else None
        is_bot_selected = selected_message and (selected_message["from_id"] < 0)

        karma_enabled = event["peer_id"] in config.chats_karma_whitelist
        group_chat = event["peer_id"] >= CHAT_ID_OFFSET

        if group_chat:
            if karma_enabled:
                match = regex.match(patterns.KARMA, message)
                if match:
                    return self.send_karma(event, selected_user if selected_user else user, not selected_user)
                match = regex.match(patterns.TOP, message)
                if match:
                    maximum_users = match.group("maximum_users")
                    maximum_users = int(maximum_users) if maximum_users else 0
                    return self.send_top(event, maximum_users)
                match = regex.match(patterns.BOTTOM, message)
                if match:
                    maximum_users = match.group("maximum_users")
                    maximum_users = int(maximum_users) if maximum_users else 0
                    return self.send_bottom(event, maximum_users)
                match = regex.match(patterns.APPLY_KARMA, message)
                if match:
                    # Only regular users can be selected
                    if is_bot_selected:
                        return

                    if not selected_user:
                        selected_user_id = match.group("selectedUserId")
                        if selected_user_id:
                            selected_user = self.base.autoInstall(int(selected_user_id), self)

                    if selected_user and (user.uid != selected_user.uid):
                        operator = match.group("operator")[0]
                        amount = match.group("amount")
                        amount = int(amount) if amount else 0

                        utcnow = datetime.utcnow()

                        # Downvotes disabled for users with negative karma
                        if (operator == "-") and (user.karma < 0):
                            self.delete_message(event)
                            self.send_not_enough_karma_error(event, user)
                            return

                        # Collective votes limit
                        if amount == 0:
                            utclast = datetime.fromtimestamp(float(user.last_collective_vote));
                            difference = utcnow - utclast
                            hours_difference = difference.total_seconds() / 3600;
                            hours_limit = self.get_karma_hours_limit(user.karma);
                            if hours_difference < hours_limit:
                                self.delete_message(event)
                                self.send_not_enough_hours_error(event, user, hours_limit, difference.total_seconds() / 60)
                                return

                        user_karma_change, selected_user_karma_change, collective_vote_applied, voters = self.apply_karma_change(event, user, selected_user, operator, amount)

                        if collective_vote_applied:
                            user.last_collective_vote = int(utcnow.timestamp())
                            self.base.save(user)

                        self.base.save(selected_user)

                        if user_karma_change:
                            self.base.save(user)
                        self.send_karma_change(event, user_karma_change, selected_user_karma_change, voters)
                        self.delete_message(event)
                        return
                match = regex.match(patterns.TOP_LANGUAGES, message)
                if match:
                    languages = match.group("languages")
                    return self.send_top_languages(event, languages)
            else:
                match = regex.match(patterns.PEOPLE, message)
                if match:
                    maximum_users = match.group("maximum_users")
                    maximum_users = int(maximum_users) if maximum_users else 0
                    return self.send_people(event, maximum_users)
                match = regex.match(patterns.PEOPLE_LANGUAGES, message)
                if match:
                    languages = match.group("languages")
                    return self.send_people_languages(event, languages)

        match = regex.match(patterns.HELP, message)
        if match:
            return self.send_help(event, group_chat, karma_enabled)
        match = regex.match(patterns.INFO, message)
        if match:
            return self.send_info(event, karma_enabled, selected_user if selected_user else user, not selected_user)
        match = regex.match(patterns.ADD_PROGRAMMING_LANGUAGE, message)
        if match:
            language = match.group('language')
            language = self.get_default_programming_language(language)
            if not language:
                return
            if language not in user.programming_languages:
                user.programming_languages.append(language)
                self.base.save(user)
            return self.send_programming_languages_list(event, user)
        match = regex.match(patterns.REMOVE_PROGRAMMING_LANGUAGE, message)
        if match:
            language = match.group('language')
            language = self.get_default_programming_language(language)
            if not language:
                return
            if language in user.programming_languages:
                user.programming_languages.remove(language)
                self.base.save(user)
            return self.send_programming_languages_list(event, user)
        match = regex.match(patterns.ADD_GITHUB_PROFILE, message)
        if match:
            profile = match.group('profile')
            if not profile:
                return
            if profile != user.github_profile:
                if requests.get(f'https://github.com/{profile}').status_code == 200:
                    user.github_profile = profile
                    self.base.save(user)
            return self.send_github_profile(event, user)
        match = regex.match(patterns.REMOVE_GITHUB_PROFILE, message)
        if match:
            profile = match.group('profile')
            if not profile:
                return
            if profile == user.github_profile:
                user.github_profile = ""
                self.base.save(user)
            return self.send_github_profile(event, user)

    def delete_message(self, event, delay=2):
        peer_id = event['peer_id']
        if peer_id in config.userbot_chats and peer_id in config.chats_deleting:
            if peer_id not in self.messages_to_delete:
                self.messages_to_delete.update({peer_id: []})

            message_id = event['conversation_message_id']
            data = {'date': datetime.now() + timedelta(seconds=delay), 'id': message_id}
            self.messages_to_delete[peer_id].append(data)

    def get_karma_hours_limit(self, karma):
        for limit_item in config.karma_limit_hours:
            if (not limit_item["min_karma"]) or (karma >= limit_item["min_karma"]):
                if (not limit_item["max_karma"]) or (karma < limit_item["max_karma"]):
                    return limit_item["limit"]
        return 168 # hours (a week)

    def apply_karma_change(self, event, user, selected_user, operator, amount):
        selected_user_karma_change = None
        user_karma_change = None
        collective_vote_applied = None
        voters = None

        # Personal karma transfer
        if amount > 0:
            if user.karma < amount:
                self.send_not_enough_karma_error(event, user)
                return user_karma_change, selected_user_karma_change, collective_vote_applied, voters
            else:
                user_karma_change = self.apply_user_karma(user, -amount)
                amount = -amount if operator == "-" else amount
                selected_user_karma_change = self.apply_user_karma(selected_user, amount)

        # Collective vote
        elif amount == 0:
            if operator == "+":
                selected_user_karma_change, voters, collective_vote_applied = self.apply_collective_vote(user, selected_user, "supporters", config.positive_votes_per_karma, +1)
            else:
                selected_user_karma_change, voters, collective_vote_applied = self.apply_collective_vote(user, selected_user, "opponents", config.negative_votes_per_karma, -1)

        return user_karma_change, selected_user_karma_change, collective_vote_applied, voters

    def apply_collective_vote(self, user, selected_user, current_voters, number_of_voters, amount):
        vote_applied = None
        if user.uid not in selected_user[current_voters]:
            selected_user[current_voters].append(user.uid)
            vote_applied = True
        if len(selected_user[current_voters]) >= number_of_voters:
            voters = selected_user[current_voters]
            selected_user[current_voters] = []
            return self.apply_user_karma(selected_user, amount), voters, vote_applied
        return None, None, vote_applied

    def apply_user_karma(self, user, amount):
        user.karma += amount
        return (user.uid, user.name, user.karma - amount, user.karma)

    def get_messages(self, event):
        reply_message = event.get("reply_message", {})
        return [reply_message] if reply_message else event.get("fwd_messages", [])

    def get_programming_languages_string_with_parentheses_or_empty(self, user):
        programming_languages_string = self.get_programming_languages_string(user)
        if programming_languages_string == "":
            return programming_languages_string
        else:
            return "(" + programming_languages_string + ")"

    def get_github_profile(self, user):
        if isinstance(user, dict):
            return user["github_profile"] if "github_profile" in user else ""
        else:
            return user.github_profile

    def get_github_profile_top_string(self, user):
        profile = self.get_github_profile(user)
        if profile:
            profile = f" — github.com/{profile}"
        return profile

    def get_programming_languages_string(self, user):
        if isinstance(user, dict):
            languages = user["programming_languages"] if "programming_languages" in user else []
        else:
            languages = user.programming_languages
        if len(languages) > 0:
            languages.sort()
            return ", ".join(languages)
        else:
            return ""

    def get_default_programming_language(self, language):
        for default_programming_language in config.default_programming_languages:
            default_programming_language = default_programming_language.replace('\\', '')
            if default_programming_language.lower() == language.lower():
                return default_programming_language
        return None

    def contains_string(self, strings, matchedString, ignoreCase):
        if ignoreCase:
            for string in strings:
                if string.lower() == matchedString.lower():
                    return True
        else:
            for string in strings:
                if string == matchedString:
                    return True
        return False

    def contains_all_strings(self, strings, matchedStrings, ignoreCase):
        matched_strings_count = len(matchedStrings)
        for string in strings:
            if self.contains_string(matchedStrings, string, ignoreCase):
                matched_strings_count -= 1
                if matched_strings_count == 0:
                    return True
        return False

    def send_karma_change(self, event, user_karma_change, selected_user_karma_change, voters):
        if selected_user_karma_change and user_karma_change:
            self.send_message(event, "Карма изменена: [id%s|%s] [%s]->[%s], [id%s|%s] [%s]->[%s]." % (user_karma_change + selected_user_karma_change))
        elif selected_user_karma_change:
            self.send_message(event, "Карма изменена: [id%s|%s] [%s]->[%s]. Голосовали: (%s)" % (selected_user_karma_change + (", ".join([f"@id{voter}" for voter in voters]),)))

    def send_karma(self, event, user, is_self=True):
        if is_self:
            response = "[id%s|%s], Ваша карма — %s."
        else:
            response = "Карма [id%s|%s] — %s."
        self.send_message(event, response % (user.uid, user.name, self.get_karma_string(user)))

    def send_info(self, event, karma_enabled, user, is_self=True):
        programming_languages_string = self.get_programming_languages_string(user)
        if not programming_languages_string:
            programming_languages_string = "отсутствуют"
        profile = self.get_github_profile(user)
        if not profile:
            profile = "отсутствует"
        else:
            profile = f"github.com/{profile}"
        if karma_enabled:
            if is_self:
                response = "[id%s|%s], Ваша карма — %s.\nВаши языки программирования: %s\nВаша страничка на GitHub — %s"
            else:
                response = "Карма [id%s|%s] — %s.\nЯзыки программирования: %s\nCтраничка на GitHub — %s"
            return self.send_message(event, response % (user.uid, user.name, self.get_karma_string(user), programming_languages_string, profile))
        else:
            if is_self:
                response = "[id%s|%s], \nВаши языки программирования: %s\nВаша страничка на GitHub — %s"
            else:
                response = "[id%s|%s]. \nЯзыки программирования: %s\nCтраничка на GitHub — %s"
            return self.send_message(event, response % (user.uid, user.name, programming_languages_string, profile))

    def get_karma_string(self, user):
        plus_string = ""
        minus_string = ""
        if isinstance(user, dict):
            karma = user["karma"]
            plus_votes = len(user["supporters"])
            minus_votes = len(user["opponents"])
        else:
            karma = user.karma
            plus_votes = len(user.supporters)
            minus_votes = len(user.opponents)
        if plus_votes > 0:
            plus_string = "+%.1f" % (plus_votes / config.positive_votes_per_karma)
        if minus_votes > 0:
            minus_string = "-%.1f" % (minus_votes / config.negative_votes_per_karma)
        if plus_votes > 0 or minus_votes > 0:
            return f"[{karma}][{plus_string}{minus_string}]"
        else:
            return f"[{karma}]"

    def send_top_users(self, event, users):
        if not users:
            return
        user_strings = ["%s [id%s|%s]%s %s" % (self.get_karma_string(user), user["uid"], user["name"], self.get_github_profile_top_string(user), self.get_programming_languages_string_with_parentheses_or_empty(user)) for user in users]
        total_symbols = 0
        i = 0
        for user_string in user_strings:
            user_string_length = len(user_string)
            if (total_symbols + user_string_length + 2) >= 4096: # Maximum message size for VK API (messages.send)
                user_strings = user_strings[:i]
            else:
                total_symbols += user_string_length + 2
                i += 1
        response = "\n".join(user_strings)
        self.send_message(event, response)

    def get_users_sorted_by_karma(self, peer_id):
        members = self.get_members_ids(peer_id);
        users = self.base.getSortedByKeys("karma", otherKeys=["programming_languages", "supporters", "opponents", "github_profile", "uid"])
        if members:
            users = [u for u in users if u["uid"] in members]
        return users

    def get_users_sorted_by_name(self, peer_id):
        members = self.get_members_ids(peer_id);
        users = self.base.getSortedByKeys("name", otherKeys=["programming_languages", "github_profile", "uid"])
        if members:
            users = [u for u in users if u["uid"] in members]
        users.reverse()
        return users

    def send_bottom(self, event, maximum_users):
        peer_id = event["peer_id"]
        users = self.get_users_sorted_by_karma(peer_id)
        users = [i for i in users if (i["karma"] != 0) or ("programming_languages" in i and len(i["programming_languages"]) > 0)]
        if (maximum_users > 0) and (len(users) >= maximum_users):
            users.reverse()
            self.send_top_users(event, users[:maximum_users])
        else:
            self.send_top_users(event, reversed(users))
    
    def send_people_users(self, event, users):
        if not users:
            return
        user_strings = ["[id%s|%s]%s %s" % (user["uid"], user["name"], self.get_github_profile_top_string(user), self.get_programming_languages_string_with_parentheses_or_empty(user)) for user in users]
        total_symbols = 0
        i = 0
        for user_string in user_strings:
            user_string_length = len(user_string)
            if (total_symbols + user_string_length + 2) >= 4096: # Maximum message size for VK API (messages.send)
                user_strings = user_strings[:i]
            else:
                total_symbols += user_string_length + 2
                i += 1
        response = "\n".join(user_strings)
        self.send_message(event, response)

    def send_people(self, event, maximum_users):
        peer_id = event["peer_id"]
        users = self.get_users_sorted_by_name(peer_id)
        users = [i for i in users if i["github_profile"] or ("programming_languages" in i and len(i["programming_languages"]) > 0)]
        if (maximum_users > 0) and (len(users) >= maximum_users):
            self.send_people_users(event, users[:maximum_users])
        else:
            self.send_people_users(event, users)

    def send_top(self, event, maximum_users):
        peer_id = event["peer_id"]
        users = self.get_users_sorted_by_karma(peer_id)
        users = [i for i in users if (i["karma"] != 0) or ("programming_languages" in i and len(i["programming_languages"]) > 0)]
        if (maximum_users > 0) and (len(users) >= maximum_users):
            self.send_top_users(event, users[:maximum_users])
        else:
            self.send_top_users(event, users)

    def send_people_languages(self, event, languages):
        languages = regex.split(r"\s+", languages)
        peer_id = event["peer_id"]
        users = self.get_users_sorted_by_name(peer_id)
        users = [i for i in users if ("programming_languages" in i and len(i["programming_languages"]) > 0) and self.contains_all_strings(i["programming_languages"], languages, True)]
        self.send_people_users(event, users)

    def send_top_languages(self, event, languages):
        languages = regex.split(r"\s+", languages)
        peer_id = event["peer_id"]
        users = self.get_users_sorted_by_karma(peer_id)
        users = [i for i in users if ("programming_languages" in i and len(i["programming_languages"]) > 0) and self.contains_all_strings(i["programming_languages"], languages, True)]
        self.send_top_users(event, users)

    def send_github_profile(self, event, user):
        profile = self.get_github_profile(user)
        if not profile:
            self.send_message(event, f"[id{user.uid}|{user.name}], у Вас не указана страничка на GitHub.")
        else:
            self.send_message(event, f"[id{user.uid}|{user.name}], Ваша страничка на GitHub — github.com/{profile}.")

    def send_programming_languages_list(self, event, user):
        programming_languages_string = self.get_programming_languages_string(user)
        if not programming_languages_string:
            self.send_message(event, f"[id{user.uid}|{user.name}], у Вас не указано языков программирования.")
        else:
            self.send_message(event, f"[id{user.uid}|{user.name}], Ваши языки программирования: {programming_languages_string}.")

    def send_help(self, event, group_chat, karma_enabled):
        if group_chat:
            if karma_enabled:
                self.send_message(event, config.help_string_with_karma)
            else:
                self.send_message(event, config.help_string)
        else:
            self.send_message(event, config.help_string_private_chat)

    def send_not_in_whitelist(self, event, user):
        peer_id = event["peer_id"]
        message = f"Извините, [id{user.uid}|{user.name}], но Ваша беседа [{peer_id}] отсутствует в белом списке для начисления кармы."
        self.send_message(event, message)

    def send_not_enough_karma_error(self, event, user):
        message = f"Извините, [id{user.uid}|{user.name}], но Вашей кармы [{user.karma}] недостаточно :("
        self.send_message(event, message)

    def send_not_enough_hours_error(self, event, user, hours_limit, difference_minutes):
        message = f"Извините, [id{user.uid}|{user.name}], но с момента вашего последнего голоса ещё не прошло {hours_limit} ч. :( До следующего голоса осталось {int(hours_limit * 60 - difference_minutes)} м."
        self.send_message(event, message)

    def get_members(self, peer_id):
        return self.messages.getConversationMembers(peer_id=peer_id)

    def get_members_ids(self, peer_id):
        members = self.get_members(peer_id)
        if "error" in members:
            return None
        else:
            return [m["member_id"] for m in members["response"]["items"] if m["member_id"] > 0]

    def send_message(self, event, message):
        self.messages.send(message=message, peer_id=event["peer_id"], disable_mentions=1, random_id=0)
Пример #2
0
class Bot(Vk):
    """Provides working with VK API as group.
    """
    def __init__(
        self,
        token: str,
        group_id: int,
        debug: bool = True
    ):
        """Auth as VK group and register commands.
        """
        Vk.__init__(self, token=token, group_id=group_id, debug=debug)
        self.messages_to_delete = {}
        self.userbot = UserBot()
        self.data = BetterBotBaseDataService()
        self.commands = Commands(self, self.data)
        self.commands.register_cmds(
            (patterns.HELP, self.commands.help_message),
            (patterns.INFO, self.commands.info_message),
            (patterns.UPDATE, self.commands.update_command),
            (patterns.ADD_PROGRAMMING_LANGUAGE,
             lambda: self.commands.change_programming_language(True)),
            (patterns.REMOVE_PROGRAMMING_LANGUAGE,
             lambda: self.commands.change_programming_language(False)),
            (patterns.ADD_GITHUB_PROFILE,
             lambda: self.commands.change_github_profile(True)),
            (patterns.REMOVE_GITHUB_PROFILE,
             lambda: self.commands.change_github_profile(False)),
            (patterns.KARMA, self.commands.karma_message),
            (patterns.TOP, self.commands.top),
            (patterns.PEOPLE, self.commands.top),
            (patterns.BOTTOM,
             lambda: self.commands.top(True)),
            (patterns.TOP_LANGUAGES, self.commands.top_langs),
            (patterns.PEOPLE_LANGUAGES, self.commands.top_langs),
            (patterns.BOTTOM_LANGUAGES,
             lambda: self.commands.top_langs(True)),
            (patterns.APPLY_KARMA, self.commands.apply_karma),
        )

    def message_new(
        self,
        event: Dict[str, Any]
    ) -> NoReturn:
        """Handling all new messages.
        """
        event = event["object"]["message"]
        msg = event["text"].lstrip("/")
        peer_id = event["peer_id"]
        from_id = event["from_id"]
        msg_id = event["conversation_message_id"]

        if peer_id in self.messages_to_delete:
            peer = CHAT_ID_OFFSET + config.USERBOT_CHATS[peer_id]
            new_messages_to_delete = []
            ids = []

            for item in self.messages_to_delete[peer_id]:
                if item['date'] > datetime.now():
                    new_messages_to_delete.append(item)
                else:
                    ids.append(item['id'])

            if new_messages_to_delete:
                self.messages_to_delete[peer_id] = new_messages_to_delete
            else:
                self.messages_to_delete.pop(peer_id)

            if ids:
                self.userbot.delete_messages(ids, peer)

        user = self.data.get_user(from_id, self) if from_id > 0 else None

        messages = self.get_messages(event)
        selected_message = messages[0] if len(messages) == 1 else None
        selected_user = (
            self.data.get_user(selected_message["from_id"], self)
            if selected_message else None)

        self.commands.process(
            msg, peer_id, from_id, messages, msg_id,
            user, selected_user)


    def delete_message(
        self,
        peer_id: int,
        msg_id: int,
        delay: int = 2
    ) -> NoReturn:
        """Assigns messages to deleting.
        """
        if peer_id in config.USERBOT_CHATS and peer_id in config.CHATS_DELETING:
            if peer_id not in self.messages_to_delete:
                self.messages_to_delete.update({peer_id: []})
            data = {
                'date': datetime.now() + timedelta(seconds=delay),
                'id': msg_id
            }
            self.messages_to_delete[peer_id].append(data)

    def get_members(
        self,
        peer_id: int
    ) -> Dict[str, Any]:
        """Returns all conversation members.
        """
        return self.messages.getConversationMembers(peer_id=peer_id)

    def get_members_ids(
        self,
        peer_id: int
    ) -> List[int]:
        """Returns all conversation member's IDs
        """
        members = self.get_members(peer_id)
        if "error" in members:
            return None
        return [m["member_id"]
                for m in members["response"]["items"] if m["member_id"] > 0]

    def send_msg(
        self,
        msg: str,
        peer_id: int
    ) -> NoReturn:
        """Sends message to chat with {peer_id}.

        :param msg: message text
        :param peer_id: chat ID
        """
        self.messages.send(
            message=msg, peer_id=peer_id,
            disable_mentions=1, random_id=0
        )

    def get_user_name(
        self,
        uid: int,
        name_case: str = "nom"
    ) -> str:
        """Returns user firstname.

        :param uid: user ID
        :param name_case: The declension case for the user's first and last name.
            Possible values:
            • Nominative – nom,
            • Genitive – gen,
            • dative – dat,
            • accusative – acc,
            • instrumental – ins,
            • prepositional – abl.
        """
        return self.users.get(user_ids=uid, name_case=name_case)['response'][0]["first_name"]

    @staticmethod
    def get_messages(
        event: Dict[str, Any]
    ) -> List[Dict[str, Any]]:
        """Returns forward messages or reply message if available.
        """
        reply_message = event.get("reply_message", {})
        return [reply_message] if reply_message else event.get("fwd_messages", [])
Пример #3
0
class V(Vk):
    def __init__(self):
        Vk.__init__(self, token=BotToken, group_id=bot_group_id, debug=True)
        self.messages_to_delete = {}
        self.userbot = UserBot()
        self.debug = True

    def message_new(self, event):
        """
        Handling all new messages.
        """
        event = event["object"]["message"]

        if event['peer_id'] in self.messages_to_delete:
            peer = 2000000000 + userbot_chats[event['peer_id']]
            new_messages_to_delete = []
            ids = []

            for item in self.messages_to_delete[event['peer_id']]:
                if item['date'] > datetime.now():
                    new_messages_to_delete.append(item)
                else:
                    ids.append(item['id'])

            if new_messages_to_delete:
                self.messages_to_delete[
                    event['peer_id']] = new_messages_to_delete
            else:
                self.messages_to_delete.pop(event['peer_id'])

            if ids:
                self.userbot.delete_messages(ids, peer)

        user = base.autoInstall(event["from_id"],
                                self) if event["from_id"] > 0 else None

        message = event["text"].lstrip("/")
        messages = self.get_messages(event)
        selected_message = messages[0] if len(messages) == 1 else None
        selected_user = base.autoInstall(selected_message["from_id"],
                                         self) if selected_message else None
        is_bot_selected = selected_message and (selected_message["from_id"] <
                                                0)

        if regex.findall(patterns.HELP, message):
            self.send_help(event)
        elif regex.findall(patterns.KARMA, message):
            self.send_karma(event, selected_user if selected_user else user,
                            not selected_user)
        elif regex.findall(patterns.TOP, message):
            self.send_top(event)
        elif regex.findall(patterns.BOTTOM, message):
            self.send_bottom(event)
        elif regex.findall(patterns.INFO, message):
            self.send_info(event, selected_user if selected_user else user,
                           not selected_user)
        elif regex.findall(patterns.APPLY_KARMA, message):
            # Only for chat rooms
            if event["peer_id"] < 2000000000:
                return
            # Only for whitelisted chat rooms
            if event["peer_id"] not in chats_whitelist:
                self.send_not_in_whitelist(event, user)
                return
            # Only regular users can be selected
            if is_bot_selected:
                return

            if selected_user and (user.uid != selected_user.uid):
                match = regex.match(patterns.APPLY_KARMA, message)
                operator = match.group("operator")[0]
                amount = match.group("amount")

                # Downvotes disabled for users with negative rating
                if (operator == "-") and (user.rating < 0):
                    self.send_not_enough_karma_error(event, user)
                    return

                user_karma_change, selected_user_karma_change, voters = self.apply_karma_change(
                    event, user, selected_user, operator, amount)
                base.save(selected_user)
                if user_karma_change:
                    base.save(user)
                self.send_karma_change(event, user_karma_change,
                                       selected_user_karma_change, voters)
                self.delete_message(event)
        elif regex.findall(patterns.ADD_PROGRAMMING_LANGUAGE, message):
            language = regex.match(patterns.ADD_PROGRAMMING_LANGUAGE,
                                   message).group('language')
            language = self.get_default_programming_language(language)
            if not language:
                return
            if "programming_languages" not in user.obj:
                user.programming_languages = []
                base.save(user)
            if language not in user.programming_languages:
                user.programming_languages.append(language)
                base.save(user)
            self.send_programming_languages_list(event, user)
        elif regex.findall(patterns.REMOVE_PROGRAMMING_LANGUAGE, message):
            language = regex.match(patterns.REMOVE_PROGRAMMING_LANGUAGE,
                                   message).group('language')
            language = self.get_default_programming_language(language)
            if not language:
                return
            if "programming_languages" not in user.obj:
                user.programming_languages = []
                base.save(user)
            if language in user.programming_languages:
                user.programming_languages.remove(language)
                base.save(user)
            self.send_programming_languages_list(event, user)
        elif regex.findall(patterns.TOP_LANGUAGES, message):
            match = regex.match(patterns.TOP_LANGUAGES, message)
            languages = match.group("languages")
            self.send_top_languages(event, languages)

    def delete_message(self, event, delay=2):
        peer_id = event['peer_id']
        if peer_id in userbot_chats and peer_id in chats_deleting:
            if peer_id not in self.messages_to_delete:
                self.messages_to_delete.update({peer_id: []})

            message_id = event['conversation_message_id']
            data = {
                'date': datetime.now() + timedelta(seconds=delay),
                'id': message_id
            }
            self.messages_to_delete[peer_id].append(data)

    def apply_karma_change(self, event, user, selected_user, operator, amount):
        selected_user_karma_change = None
        user_karma_change = None
        voters = None

        amount = int(amount) if amount else 0

        # Personal karma transfer
        if amount > 0:
            if user.rating < amount:
                self.send_not_enough_karma_error(event, user)
                return user_karma_change, selected_user_karma_change, voters
            else:
                user_karma_change = self.apply_user_karma(user, -amount)
                amount = -amount if operator == "-" else amount
                selected_user_karma_change = self.apply_user_karma(
                    selected_user, amount)

        # Collective vote
        elif amount == 0:
            if operator == "+":
                selected_user_karma_change, voters = self.apply_collective_vote(
                    user, selected_user, "current", 2, +1)
            else:
                selected_user_karma_change, voters = self.apply_collective_vote(
                    user, selected_user, "current_sub", 3, -1)

        return user_karma_change, selected_user_karma_change, voters

    def apply_collective_vote(self, user, selected_user, current_voters,
                              number_of_voters, amount):
        if user.uid not in selected_user[current_voters]:
            selected_user[current_voters].append(user.uid)
        if len(selected_user[current_voters]) >= number_of_voters:
            voters = selected_user[current_voters]
            selected_user[current_voters] = []
            return self.apply_user_karma(selected_user, amount), voters
        return None, None

    def apply_user_karma(self, user, amount):
        user.rating += amount
        return (user.uid, user.name, user.rating - amount, user.rating)

    def get_messages(self, event):
        reply_message = event.get("reply_message", {})
        return [reply_message] if reply_message else event.get(
            "fwd_messages", [])

    def get_programming_languages_string_with_parentheses_or_empty(self, user):
        programming_languages_string = self.get_programming_languages_string(
            user)
        if programming_languages_string == "":
            return programming_languages_string
        else:
            return "(" + programming_languages_string + ")"

    def get_programming_languages_string(self, user):
        if isinstance(user, dict):
            languages = user["programming_languages"] if "programming_languages" in user else []
        else:
            languages = user.programming_languages
        if len(languages) > 0:
            languages.sort()
            return ", ".join(languages)
        else:
            return ""

    def get_default_programming_language(self, language):
        for default_programming_language in default_programming_languages:
            default_programming_language = default_programming_language.replace(
                '\\', '')
            if default_programming_language.lower() == language.lower():
                return default_programming_language
        return None

    def contains_string(self, strings, matchedString, ignoreCase):
        if ignoreCase:
            for string in strings:
                if string.lower() == matchedString.lower():
                    return True
        else:
            for string in strings:
                if string == matchedString:
                    return True
        return False

    def contains_all_strings(self, strings, matchedStrings, ignoreCase):
        matchedStringsCount = len(matchedStrings)
        for string in strings:
            if self.contains_string(matchedStrings, string, ignoreCase):
                matchedStringsCount -= 1
                if matchedStringsCount == 0:
                    return True
        return False

    def send_karma_change(self, event, user_karma_change,
                          selected_user_karma_change, voters):
        if selected_user_karma_change and user_karma_change:
            self.send_message(
                event,
                "Карма изменена: [id%s|%s] [%s]->[%s], [id%s|%s] [%s]->[%s]." %
                (user_karma_change + selected_user_karma_change))
        elif selected_user_karma_change:
            self.send_message(
                event,
                "Карма изменена: [id%s|%s] [%s]->[%s]. Голосовали: (%s)" %
                (selected_user_karma_change +
                 (", ".join(["@id%s" % (voter) for voter in voters]), )))

    def send_karma(self, event, user, is_self=True):
        if is_self:
            response = "[id%s|%s], Ваша карма - [%s]."
        else:
            response = "Карма [id%s|%s] - [%s]."
        self.send_message(event, response % (user.uid, user.name, user.rating))

    def send_info(self, event, user, is_self=True):
        programming_languages_string = self.get_programming_languages_string(
            user)
        if is_self:
            response = "[id%s|%s], Ваша карма - [%s].\nВаши языки программирования - %s"
        else:
            response = "Карма [id%s|%s] - [%s].\nЯзыки программирования - %s"
        if not programming_languages_string:
            programming_languages_string = "отсутствуют"
        self.send_message(
            event, response %
            (user.uid, user.name, user.rating, programming_languages_string))

    def send_top_users(self, event, users):
        if not users:
            return
        user_strings = [
            "[%s] [id%s|%s] %s" %
            (user["rating"], user["uid"], user["name"],
             self.get_programming_languages_string_with_parentheses_or_empty(
                 user)) for user in users
        ]

        total_symbols = 0
        i = 0
        for user_string in user_strings:
            user_string_length = len(user_string)
            if (total_symbols + user_string_length + 2
                ) >= 4096:  # Maximum message size for VK API (messages.send)
                user_strings = user_strings[:i]
            else:
                total_symbols += user_string_length + 2
                i += 1

        response = "\n".join(user_strings)
        self.send_message(event, response)

    def send_bottom(self, event):
        users = base.getSortedByKeys("rating",
                                     otherKeys=["programming_languages"])
        users = [
            i for i in users
            if (i["rating"] != 0) or ("programming_languages" in i
                                      and len(i["programming_languages"]) > 0)
        ]
        self.send_top_users(event, reversed(users))

    def send_top(self, event):
        users = base.getSortedByKeys("rating",
                                     otherKeys=["programming_languages"])
        users = [
            i for i in users
            if (i["rating"] != 0) or ("programming_languages" in i
                                      and len(i["programming_languages"]) > 0)
        ]
        self.send_top_users(event, users)

    def send_top_languages(self, event, languages):
        languages = regex.split(r"\s+", languages)
        users = base.getSortedByKeys("rating",
                                     otherKeys=["programming_languages"])
        users = [
            i for i in users
            if ("programming_languages" in i
                and len(i["programming_languages"]) > 0) and self.
            contains_all_strings(i["programming_languages"], languages, True)
        ]
        self.send_top_users(event, users)

    def send_programming_languages_list(self, event, user):
        programming_languages_string = self.get_programming_languages_string(
            user)
        if not programming_languages_string:
            self.send_message(
                event, "[id%s|%s], у Вас не указано языков программирования." %
                (user.uid, user.name))
        else:
            self.send_message(
                event, "[id%s|%s], Ваши языки программирования: %s." %
                (user.uid, user.name, programming_languages_string))

    def send_help(self, event):
        self.send_message(event, help_string)

    def send_not_in_whitelist(self, event, user):
        self.send_message(
            event,
            "Извините, [id%s|%s], но Ваша беседа [%s] отсутствует в белом списке для начисления кармы."
            % (user.uid, user.name, event["peer_id"]))

    def send_not_enough_karma_error(self, event, user):
        self.send_message(
            event, "Извините, [id%s|%s], но Вашей кармы [%s] недостаточно :(" %
            (user.uid, user.name, user.rating))

    def send_message(self, event, message):
        self.messages.send(message=message,
                           peer_id=event["peer_id"],
                           disable_mentions=1,
                           random_id=0)