Пример #1
0
class SocialCreditHandler(BaseHandler):
    TRANSACTIONS = {
        '😄':
        Transaction(20, 'Good! {username} Social Credit Score is now {score}'),
        '😞':
        Transaction(
            -20,
            'Public shame! {username} Social Credit Score is now {score}'),
    }

    def change_score(self):
        assessed_profile = self.get_assessed_profile()
        issuer_profile = self.get_profile()
        if assessed_profile == issuer_profile:
            raise exceptions.SocialCreditError(
                'Ranking your own messages is not allowed')
        # TODO put message_id as a top-level param in Transaction,
        # bind it together with issuer as an UniqueTogether,
        # then remove this check
        if ProfileTransaction.objects(
                message__message_id=self.message.reply_to_message.message_id,
                issuer=issuer_profile).first():
            raise exceptions.SocialCreditError(
                'You have already ranked this message')
        transaction = self.TRANSACTIONS[self.message.sticker.emoji]
        assessed_profile.change_score(
            score_delta=transaction.amount,
            issuer=issuer_profile,
            message=self.message.reply_to_message.json,
        )
        reaction = transaction.message_template
        self.send_reaction(reaction,
                           str_format={
                               'username':
                               f'@{assessed_profile.tg_username}'
                               if assessed_profile.tg_username else
                               assessed_profile.tg_full_name,
                               'score':
                               assessed_profile.current_score,
                           })

    @BaseHandler.run_validators([
        message_validators.validate_chat_not_exist,
        message_validators.validate_user_is_admin,
    ])
    def register_chat(self):
        self.chat = Chat(tg_chat_id=self.message.chat.id).save()
        self.send_system(
            'Chat is sucessfully registered in Social Credit system')

    @BaseHandler.run_validators(
        [message_validators.validate_profile_does_not_exist])
    def create_profile(self):
        user_id = self.message.from_user.id
        profile = ChatUserProfile(
            chat=self.chat,
            tg_user_id=self.message.from_user.id,
            tg_first_name=self.message.from_user.first_name,
            tg_last_name=self.message.from_user.last_name,
            tg_username=self.message.from_user.username,
        )
        profile.save()
        profile.change_score(self.chat.starting_score, DEFAULT_ISSUER,
                             {'date': self.message.date})
        self.send_system(
            'You have successfully enrolled in Social Credit system')

    @BaseHandler.run_validators([message_validators.validate_profile_exist])
    def get_profile_score(self):
        profile = self.get_profile()
        user_id = self.message.from_user.id
        self.send_system(
            'Your Social Credit score is {score}',
            str_format={'score': profile.current_score},
        )

    def get_profile_infos(self):
        profiles = self.chat.get_profiles(order_by=('-current_score', ))
        profile_infos = []
        for profile in profiles:
            profile_infos.append(
                f'@{profile.tg_username}: {profile.current_score}' if profile.
                tg_username else
                f'{profile.tg_full_name}: {profile.current_score}')
        return profile_infos

    def get_chat_info(self):
        profile_infos = self.get_profile_infos()
        self.send_system(
            'Current Social Credit scores are:\n{profile_infos}',
            str_format={'profile_infos': '\n'.join(profile_infos)},
        )

    def get_help(self):
        start_score = self.chat.starting_score if self.chat else DEFAULT_SCORE
        self.send_system(
            '''I am a control bot of Social Credit Score system for Telegram group chats.
Social Credit Score is manipulated via replies to rankable messages, using this sticker set: https://t.me/addstickers/PoohSocialCredit.
Before use, Social Credit System must be enabled for your chat by a Chat Admin, using /social_credit_enable command.
Then, chat members can enroll to Social Credit System using /social_credit_enroll command.
After enrolling, each member has a {start_score} Starting Credit Score (This can be changed by an admin).
/social_credit_myscore shows your current Social Credit score, while /social_credit_chatinfo command show score info on everyone enrolled into system in this chat.
/social_credit_get_top_raters shows top 10 raters in your chat.
Admins can get help on admin commands using /social_credit_admin_help.
/social_credit_help displays this message again.''',
            str_format={'start_score': start_score},
        )

    @BaseHandler.run_validators([message_validators.validate_user_is_admin])
    def get_admin_help(self):
        languages = ', '.join(self.bot.translator.languages)
        self.send_system('''Admin can use following commands:
    * /social_credit_enable - enables Social Credit System for this chat
    * /social_credit_set_chat_option - set a value for a chat option. Takes 2 params: option_name and option value, separated by spaces. Example: `/social_credit_set_chat_option starting_score 300`

Avaliable chat options:
    * starting_score - starting score for anyone, who enrolls to Social Credit System. Default is {start_score}
    * verbosity - bot verbosity level. Can be 0 - answer only to commands, 1 - allow periodical messages, 2 - allow bot reactions. Default is {verbosity}
    * language - bot messages language. Available languages are {languages}. Default is `{language}`, however this can fallback to `en`

For plugins, following management commands are available:
    * /social_credit_plugins - show list of available plugins.
    * /social_credit_enable_plugin - enables plugin. Takes 1 parameter `plugin_name`.
    * /social_credit_disable_plugin - disables plugin. Takes 1 parameter `plugin_name`.
    * /social_credit_plugin_help - shows help text for plugin. Takes 1 parameter `plugin_name`.''',
                         str_format={
                             'start_score': DEFAULT_SCORE,
                             'verbosity': DEFAULT_VERBOSITY,
                             'language': DEFAULT_LANGUAGE,
                             'languages': languages
                         })

    @BaseHandler.run_validators([
        message_validators.validate_user_is_admin,
        message_validators.validate_two_params,
    ])
    def set_chat_option(self):
        option, value = self.message.text.split(' ')[1:]
        try:
            self.chat.set_option(option, value)
        except mongoengine.ValidationError as e:
            error_message = e._format_errors().split(':')[0]
            # TODO refine translation process of those kind of exceptions
            # (where template is translated first, and then formatting is applied).
            # Handler logic shouldn't worry about translations
            raise exceptions.SocialCreditError(
                'Invalid value for option {option}: {error_message}'.format(
                    option=option, error_message=error_message), )
        except mongoengine.FieldDoesNotExist:
            raise exceptions.SocialCreditError(
                'Unknown option {option}'.format(option=option))
        self.send_system('Option {option} set to {value}',
                         str_format={
                             'option': option,
                             'value': value
                         })

    @BaseHandler.run_validators([message_validators.validate_user_is_admin])
    def get_plugins(self):
        plugins = map(lambda plugin: f'* {plugin}', get_plugin_list())
        self.send_system('Available plugins: \n{plugins}',
                         str_format={'plugins': '\n'.join(plugins)})

    @BaseHandler.run_validators([
        message_validators.validate_user_is_admin,
        message_validators.validate_one_param,
        message_validators.validate_plugin_exists,
    ])
    def enable_plugin(self):
        plugin = self.message.text.split(' ')[1]
        import_module(f'plugins.{plugin}').enable(self.chat)
        self.send_system(
            'Plugin {plugin} is enabled.',
            str_format={'plugin': plugin},
        )

    @BaseHandler.run_validators([
        message_validators.validate_user_is_admin,
        message_validators.validate_one_param,
        message_validators.validate_plugin_exists,
    ])
    def disable_plugin(self):
        plugin = self.message.text.split(' ')[1]
        import_module(f'plugins.{plugin}').disable(self.chat)
        self.send_system(
            'Plugin {plugin} is disabled.',
            str_format={'plugin': plugin},
        )

    @BaseHandler.run_validators([
        message_validators.validate_user_is_admin,
        message_validators.validate_three_params,
        message_validators.validate_plugin_exists,
    ])
    def set_plugin_option(self):
        plugin, option, value = self.message.text.split(' ')[1:]
        try:
            setattr(self.chat.plugin_options.get(plugin_name=plugin), option,
                    value)
            self.chat.plugin_options.save()
        except mongoengine.DoesNotExist:
            # TODO looks like this is never raised for EmbeddedDocuments,
            # need to investigate and somehow implement manual check
            raise exceptions.SocialCreditError('Invalid option')
        except mongoengine.ValidationError as e:
            error_message = e._format_errors().split(':')[0]
            raise exceptions.SocialCreditError(error_message)
        self.send_system(
            '{plugin} plugin option {option} is set to {value}',
            str_format={
                'plugin': plugin,
                'option': option,
                'value': value
            },
        )

    @BaseHandler.run_validators([
        message_validators.validate_user_is_admin,
        message_validators.validate_one_param,
        message_validators.validate_plugin_exists,
    ])
    def get_plugin_help(self):
        plugin = self.message.text.split(' ')[1]
        text, str_format = import_module(f'plugins.{plugin}').get_help()
        self.send_system(
            text,
            str_format=str_format,
        )

    def get_top_raters(self):
        top_count = 10
        profiles = self.chat.get_profiles()
        pipeline_profiles = list(profiles.values_list('id'))
        pipeline = [
            {
                "$match": {
                    "issuer": {
                        "$in": pipeline_profiles
                    }
                }
            },
            {
                "$sortByCount": "$issuer"
            },
        ]
        cursor = ProfileTransaction.objects.aggregate(pipeline)
        top = []
        i = 0
        while i != top_count and cursor.alive:
            rater = cursor.next()
            profile = profiles.get(id=rater['_id'])
            top.append(
                f'@{profile.tg_username}: {rater["count"]}' if profile.
                tg_username else f'{profile.tg_full_name}: {rater["count"]}')
            i += 1
        self.send_system(
            'Current Social Credit top raters {top_count} are:\n{top}',
            str_format={
                'top_count': top_count,
                'top': '\n'.join(top)
            },
        )