예제 #1
0
 def __init__(self, bot, command, plugin_info, command_info):
     self.bot = bot
     self.db = self.bot.db
     self.cd = CommandRateLimiter(self)
     self.command = command
     self.plugin_info = plugin_info
     self.command_info = command_info
     self.name = self.command_info.get('name')
     self.path = self.command_info.get('path')
     self.category = self.plugin_info.get('category')
     self.subcategory = self.plugin_info.get('subcategory')
     self.log = create_logger(self.name.upper())
     self.nsfw = False
     self.cfg = {}
     self.cache = {}
     if self.bot.cfg.pref.raw.get('elastic'):
         self.stats = ElasticHandler(self.bot.cfg.pref.raw.get('elastic'), 'sigma-command')
     else:
         self.stats = None
     self.owner = False
     self.partner = False
     self.dmable = False
     self.requirements = ['send_messages', 'embed_links']
     self.alts = []
     self.usage = '{pfx}{cmd}'
     self.desc = 'No description provided.'
     self.insert_command_info()
     self.load_command_config()
예제 #2
0
 def __init__(self, bot, command, module_info, command_info):
     """
     :type bot: sigma.core.sigma.ApexSigma
     :type command: function
     :type module_info: dict
     :type command_info: dict
     """
     self.bot = bot
     self.db = self.bot.db
     self.cd = CommandRateLimiter(self)
     self.command = command
     self.module_info = module_info
     self.command_info = command_info
     self.name = self.command_info.get('name')
     self.path = self.command_info.get('path')
     self.category = self.module_info.get('category')
     self.subcategory = self.module_info.get('subcategory')
     self.log = create_logger(self.name.upper(),
                              shards=self.bot.cfg.dsc.shards)
     self.nsfw = False
     self.cfg = ModuleConfig(self)
     self.owner = False
     self.partner = False
     self.dmable = False
     self.requirements = ['send_messages', 'embed_links']
     self.alts = []
     self.usage = '{pfx}{cmd}'
     self.desc = 'No description provided.'
     self.insert_command_info()
     self.load_command_config()
예제 #3
0
 def __init__(self, bot, command, plugin_info: dict, command_info: dict):
     self.bot = bot
     self.db: Database = self.bot.db
     self.cd = CommandRateLimiter(self)
     self.command = command
     self.plugin_info = plugin_info
     self.command_info = command_info
     self.name = self.command_info.get('name')
     self.path = self.command_info.get('path')
     self.category = self.plugin_info.get('category')
     self.subcategory = self.plugin_info.get('subcategory')
     self.log = create_logger(self.name.upper())
     self.nsfw = False
     self.cfg = {}
     self.cache = {}
     self.owner = False
     self.partner = False
     self.dmable = False
     self.requirements = ['send_messages', 'embed_links']
     self.alts = []
     self.usage = '{pfx}{cmd}'
     self.desc = 'No description provided.'
     self.insert_command_info()
     self.load_command_config()
예제 #4
0
class SigmaCommand(object):
    """
    The main command object that envelops all processes that should
    be checked before a command executes and finally execute the command.
    """

    __slots__ = ("bot", "db", "cd", "command", "module_info", "command_info",
                 "name", "path", "category", "subcategory", "log", "nsfw",
                 "cfg", "owner", "partner", "dmable", "requirements", "alts",
                 "usage", "desc")

    def __init__(self, bot, command, module_info, command_info):
        """
        :type bot: sigma.core.sigma.ApexSigma
        :type command: function
        :type module_info: dict
        :type command_info: dict
        """
        self.bot = bot
        self.db = self.bot.db
        self.cd = CommandRateLimiter(self)
        self.command = command
        self.module_info = module_info
        self.command_info = command_info
        self.name = self.command_info.get('name')
        self.path = self.command_info.get('path')
        self.category = self.module_info.get('category')
        self.subcategory = self.module_info.get('subcategory')
        self.log = create_logger(self.name.upper(),
                                 shards=self.bot.cfg.dsc.shards)
        self.nsfw = False
        self.cfg = ModuleConfig(self)
        self.owner = False
        self.partner = False
        self.dmable = False
        self.requirements = ['send_messages', 'embed_links']
        self.alts = []
        self.usage = '{pfx}{cmd}'
        self.desc = 'No description provided.'
        self.insert_command_info()
        self.load_command_config()

    def insert_command_info(self):
        """
        Inserts the command details into the class.
        """
        self.alts = self.command_info.get('alts', [])
        self.usage = self.command_info.get('usage', '{pfx}{cmd}')
        self.desc = self.command_info.get('description',
                                          'No description provided.')
        self.requirements += self.command_info.get('requirements', [])
        permissions = self.command_info.get('permissions', {})
        if permissions:
            self.nsfw = bool(permissions.get('nsfw'))
            self.owner = bool(permissions.get('owner'))
            self.partner = bool(permissions.get('partner'))
            self.dmable = bool(permissions.get('dmable'))
        if self.owner:
            self.desc += '\n(Bot Owner Only)'

    def load_command_config(self):
        """
        Grabs any details the command should contain such as API keys.
        """
        config_path = f'config/modules/{self.name}.yml'
        if os.path.exists(config_path):
            with open(config_path) as config_file:
                self.cfg.load(yaml.safe_load(config_file))

    def resource(self, res_path):
        """
        The from-root path to the resource files of the command.
        :type res_path: str
        :rtype: str
        """
        module_path = self.path
        res_path = f'{module_path}/res/{res_path}'
        res_path = res_path.replace('\\', '/')
        return res_path

    def get_exception(self):
        """
        Gets the exception to catch/except based on the mode
        that the bot is running in, dev or not dev.
        :rtype: DummyException or Exception
        """
        return DummyException if self.bot.cfg.pref.dev_mode else Exception

    def log_command_usage(self, message, args, extime):
        """
        Adds a log line when a command is used.
        :type message: discord.Message
        :type args: list[str]
        :type extime: float
        """
        crst = arrow.get(message.created_at).float_timestamp
        exdiff = round(extime - crst, 3)
        if message.guild:
            cmd_location = f'SRV: {message.guild.name} [{message.guild.id}] | '
            cmd_location += f'CHN: #{message.channel.name} [{message.channel.id}]'
        else:
            cmd_location = 'DIRECT MESSAGE'
        author_full = f'{message.author.name}#{message.author.discriminator} [{message.author.id}]'
        log_text = f'USR: {author_full} | {cmd_location}'
        if args:
            log_text += f' | ARGS: {" ".join(args)}'
        log_text += f' | EX: {exdiff}'
        self.log.info(log_text)

    async def add_usage_sumarum(self, message):
        """
        Adds a special currency type when a command is executed.
        :type message: discord.Message
        """
        trigger = f'usage_{self.name}'
        if message.guild and not await self.bot.cool_down.on_cooldown(
                trigger, message.author):
            await self.bot.cool_down.set_cooldown(trigger, message.author, 450)
            award = secrets.randbelow(3)
            if award:
                await self.db.add_resource(message.author.id, 'sumarum', award,
                                           trigger, message, True)

    @staticmethod
    async def respond_with_emote(message, icon):
        """
        Responds to a message with an emote reaction.
        :type message: discord.Message
        :type icon: discord.Emoji or str
        """
        try:
            await message.add_reaction(icon)
        except discord.DiscordException:
            pass

    async def update_cooldown(self, author):
        """
        Updates the cooldown timer of the command usage.
        :type author: discord.User
        """
        cdfile = await self.db[self.db.db_nam].CommandCooldowns.find_one(
            {'command': self.name}) or {}
        cooldown = cdfile.get('cooldown')
        if cooldown:
            await self.bot.cool_down.set_cooldown(f'{self.name}_core', author,
                                                  cooldown)

    @staticmethod
    async def check_black_args(settings, args):
        """
        Check the command request for any blacklisted arguments.
        :type settings: dict
        :type args list[str]
        :rtype: bool
        """
        black_args = settings.get('blocked_args', [])
        trigg_args = [arg for arg in args if arg in black_args]
        return any(trigg_args)

    async def check_permissions(self, payload):
        """
        Runs all permission checks and initializers.
        :type payload: sigma.core.mechanics.payload.CommandPayload
        :rtype: GlobalCommandPermissions, ServerCommandPermissions
        """
        perms = GlobalCommandPermissions(self, payload)
        await perms.check_black_usr()
        await perms.check_black_srv()
        perms.generate_response()
        perms.check_final()
        guild_perms = ServerCommandPermissions(self, payload.msg)
        await guild_perms.check_perms()
        return perms, guild_perms

    async def send_requirements_error(self, requirements, payload):
        """
        Sends an error that the command requires additional permissions to execute.
        :type requirements: sigma.core.mechanics.requirements.CommandRequirements
        :type payload: sigma.core.mechanics.payload.CommandPayload
        """
        reqs_embed = discord.Embed(color=0xBE1931)
        reqs_error_title = f'❗ {self.bot.user.name} is missing permissions!'
        reqs_error_list = ''
        for req in requirements.missing_list:
            req = req.replace('_', ' ').title()
            reqs_error_list += f'\n- {req}'
        prefix = self.db.get_prefix(payload.settings)
        reqs_embed.add_field(name=reqs_error_title,
                             value=f'```\n{reqs_error_list}\n```')
        reqs_embed.set_footer(text=f'{prefix}{self.name} could not execute.')
        try:
            await payload.msg.channel.send(embed=reqs_embed)
        except (discord.Forbidden, discord.NotFound):
            pass

    async def add_detailed_stats(self, pld, exec_timestamp):
        """
        Adds detailed statistics for command usage.
        :param pld:
        :type pld: sigma.core.mechanics.payload.CommandPayload
        :type exec_timestamp: float
        """
        cmd_stat = CommandStatistic(self.db, self, pld)
        cmd_stat.exec_timestamp = exec_timestamp
        cmd_stat.exec_time = exec_timestamp - arrow.get(
            pld.msg.created_at).float_timestamp
        await cmd_stat.save()

    async def execute(self, payload):
        """
        Runs necessary checks and executes the command function.
        :type payload: sigma.core.mechanics.payload.CommandPayload
        """
        if self.bot.ready:
            if payload.msg.guild:
                delete_command_message = payload.settings.get(
                    'delete_commands')
                if delete_command_message:
                    try:
                        await payload.msg.delete()
                    except (discord.Forbidden, discord.NotFound):
                        pass
                override = check_filter_perms(payload.msg, payload.settings,
                                              'arguments')
                if await self.check_black_args(payload.settings, payload.args):
                    if not any([
                            payload.msg.author.guild_permissions.administrator,
                            override
                    ]):
                        await self.respond_with_emote(payload.msg, '🛡')
                        return
            if not self.bot.cfg.dsc.bot and payload.msg.author.id != self.bot.user.id:
                self.log.warning(f'{payload.msg.author.name} tried using me.')
                return
            if not self.cd.is_cooling(payload.msg):
                if not await self.bot.cool_down.on_cooldown(
                        f'{self.name}_core', payload.msg.author):
                    await self.update_cooldown(payload.msg.author)
                    perms, guild_perms = await self.check_permissions(payload)
                    exec_timestamp = arrow.utcnow().float_timestamp
                    self.log_command_usage(payload.msg, payload.args,
                                           exec_timestamp)
                    self.cd.set_cooling(payload.msg)
                    if perms.permitted:
                        if guild_perms.permitted:
                            requirements = CommandRequirements(
                                self, payload.msg)
                            if requirements.reqs_met:
                                try:
                                    executed = False
                                    last_error = None
                                    client_os_broken_tries = 0
                                    while client_os_broken_tries < 3 and not executed:
                                        try:
                                            await self.add_detailed_stats(
                                                payload, exec_timestamp)
                                            await getattr(
                                                self.command,
                                                self.name)(self, payload)
                                            executed = True
                                        except CancelledError:
                                            pass
                                        except aiohttp.ClientOSError as err:
                                            last_error = err
                                            client_os_broken_tries += 1
                                            await asyncio.sleep(1)
                                    if not executed:
                                        raise last_error
                                    await add_cmd_stat(self)
                                    await self.add_usage_sumarum(payload.msg)
                                    self.bot.command_count += 1
                                    cmd_ev_pld = CommandEventPayload(
                                        self.bot, self, payload)
                                    event_task = self.bot.queue.event_runner(
                                        'command', cmd_ev_pld)
                                    self.bot.loop.create_task(event_task)
                                except self.get_exception() as e:
                                    error = SigmaError(self, e)
                                    await error.error_handler(payload)
                            else:
                                await self.respond_with_emote(payload.msg, '📝')
                                await self.send_requirements_error(
                                    requirements, payload)
                        else:
                            self.log.warning(
                                'ACCESS DENIED: This module or command is not allowed in this location.'
                            )
                            await self.respond_with_emote(payload.msg, '🔒')
                    else:
                        perms.log_unpermitted()
                        await self.respond_with_emote(payload.msg, '⛔')
                        if perms.response:
                            try:
                                await payload.msg.channel.send(
                                    embed=perms.response)
                            except (discord.Forbidden, discord.NotFound):
                                pass
                else:
                    await self.respond_with_emote(payload.msg, '❄')
            else:
                await self.respond_with_emote(payload.msg, '🕙')
예제 #5
0
class SigmaCommand(object):
    def __init__(self, bot, command, plugin_info: dict, command_info: dict):
        self.bot = bot
        self.db: Database = self.bot.db
        self.cd = CommandRateLimiter(self)
        self.command = command
        self.plugin_info = plugin_info
        self.command_info = command_info
        self.name = self.command_info.get('name')
        self.path = self.command_info.get('path')
        self.category = self.plugin_info.get('category')
        self.subcategory = self.plugin_info.get('subcategory')
        self.log = create_logger(self.name.upper())
        self.nsfw = False
        self.cfg = {}
        self.cache = {}
        self.owner = False
        self.partner = False
        self.dmable = False
        self.requirements = ['send_messages', 'embed_links']
        self.alts = []
        self.usage = '{pfx}{cmd}'
        self.desc = 'No description provided.'
        self.insert_command_info()
        self.load_command_config()

    @staticmethod
    def get_usr_data(usr: discord.User):
        usr_data = {
            'color':
            str(usr.color) if isinstance(usr, discord.Member) else '#000000',
            'created':
            str(usr.created_at),
            'discriminator':
            usr.discriminator,
            'display_name':
            usr.display_name,
            'game': (usr.activity.name if usr.activity else None)
            if isinstance(usr, discord.Member) else None,
            'id':
            usr.id,
            'name':
            usr.name,
            'status':
            str(usr.status) if isinstance(usr, discord.Member) else None
        }
        return usr_data

    def insert_command_info(self):
        self.alts = self.command_info.get('alts', [])
        self.usage = self.command_info.get('usage', '{pfx}{cmd}')
        self.desc = self.command_info.get('description',
                                          'No description provided.')
        self.requirements += self.command_info.get('requirements', [])
        permissions = self.command_info.get('permissions', {})
        if permissions:
            self.nsfw = bool(permissions.get('nsfw'))
            self.owner = bool(permissions.get('owner'))
            self.partner = bool(permissions.get('partner'))
            self.dmable = bool(permissions.get('dmable'))
        if self.owner:
            self.desc += '\n(Bot Owner Only)'

    def load_command_config(self):
        config_path = f'config/plugins/{self.name}.yml'
        if os.path.exists(config_path):
            with open(config_path) as config_file:
                self.cfg = yaml.safe_load(config_file)

    def resource(self, res_path: str):
        module_path = self.path
        res_path = f'{module_path}/res/{res_path}'
        res_path = res_path.replace('\\', '/')
        return res_path

    def get_exception(self):
        if self.bot.cfg.pref.dev_mode:
            cmd_exception = DummyException
        else:
            cmd_exception = Exception
        return cmd_exception

    def log_command_usage(self, message: discord.Message, args: list,
                          extime: int):
        crst = arrow.get(message.created_at).float_timestamp
        exdiff = round(extime - crst, 3)
        if message.guild:
            cmd_location = f'SRV: {message.guild.name} [{message.guild.id}] | '
            cmd_location += f'CHN: #{message.channel.name} [{message.channel.id}]'
        else:
            cmd_location = 'DIRECT MESSAGE'
        author_full = f'{message.author.name}#{message.author.discriminator} [{message.author.id}]'
        log_text = f'USR: {author_full} | {cmd_location}'
        if args:
            log_text += f' | ARGS: {" ".join(args)}'
        log_text += f' | EX: {exdiff}'
        self.log.info(log_text)

    # async def add_usage_exp(self, message: discord.Message):
    #     trigger = 'usage_experience'
    #     if message.guild and not await self.bot.cool_down.on_cooldown(trigger, message.author):
    #         award_xp = (600 if message.guild.large else 500) + secrets.randbelow(100)
    #         await self.db.add_resource(message.author.id, 'experience', award_xp, trigger, message, True)
    #         await self.bot.cool_down.set_cooldown(trigger, message.author, 450)

    @staticmethod
    async def respond_with_icon(message: discord.Message, icon: str
                                or discord.Emoji):
        try:
            await message.add_reaction(icon)
        except discord.DiscordException:
            pass

    async def update_cooldown(self, author):
        cdfile = await self.db[self.db.db_nam].CommandCooldowns.find_one(
            {'command': self.name}) or {}
        cooldown = cdfile.get('cooldown')
        if cooldown:
            await self.bot.cool_down.set_cooldown(f'{self.name}_core', author,
                                                  cooldown)

    async def check_black_args(self, guild: discord.Guild, args: list):
        black_args = await self.db.get_guild_settings(guild.id,
                                                      'blocked_args') or []
        trigg_args = [arg for arg in args if arg in black_args]
        return any(trigg_args)

    async def execute(self, message: discord.Message, args: list):
        if self.bot.ready:
            if message.guild:
                delete_command_message = await self.db.get_guild_settings(
                    message.guild.id, 'delete_commands')
                if delete_command_message:
                    try:
                        await message.delete()
                    except (discord.Forbidden, discord.NotFound):
                        pass
                if await self.check_black_args(message.guild, args):
                    await self.respond_with_icon(message, '🛡')
                    return
            if not self.bot.cfg.dsc.bot and message.author.id != self.bot.user.id:
                self.log.warning(f'{message.author.name} tried using me.')
                return
            if not self.cd.is_cooling(message):
                if not await self.bot.cool_down.on_cooldown(
                        f'{self.name}_core', message.author):
                    await self.update_cooldown(message.author)
                    perms = GlobalCommandPermissions(self, message)
                    await perms.check_black_usr()
                    await perms.check_black_srv()
                    await perms.generate_response()
                    perms.check_final()
                    guild_allowed = ServerCommandPermissions(self, message)
                    await guild_allowed.check_perms()
                    self.log_command_usage(message, args,
                                           arrow.utcnow().float_timestamp)
                    self.cd.set_cooling(message)
                    if perms.permitted:
                        if guild_allowed.permitted:
                            requirements = CommandRequirements(self, message)
                            if requirements.reqs_met:
                                try:
                                    await getattr(self.command,
                                                  self.name)(self, message,
                                                             args)
                                    await add_cmd_stat(self)
                                    # await self.add_usage_exp(message)
                                    self.bot.command_count += 1
                                    event_task = self.bot.queue.event_runner(
                                        'command', self, message, args)
                                    self.bot.loop.create_task(event_task)
                                except self.get_exception() as e:
                                    error = SigmaError(self, e)
                                    await error.error_handler(message, args)
                            else:
                                await self.respond_with_icon(message, '📝')
                                reqs_embed = discord.Embed(color=0xBE1931)
                                reqs_error_title = f'❗ {self.bot.user.name} is missing permissions!'
                                reqs_error_list = ''
                                for req in requirements.missing_list:
                                    req = req.replace('_', ' ').title()
                                    reqs_error_list += f'\n- {req}'
                                prefix = await self.db.get_prefix(message)
                                reqs_embed.add_field(
                                    name=reqs_error_title,
                                    value=f'```\n{reqs_error_list}\n```')
                                reqs_embed.set_footer(
                                    text=
                                    f'{prefix}{self.name} could not execute.')
                                try:
                                    await message.channel.send(embed=reqs_embed
                                                               )
                                except (discord.Forbidden, discord.NotFound):
                                    pass
                        else:
                            self.log.warning(
                                'ACCESS DENIED: This module or command is not allowed in this location.'
                            )
                            await self.respond_with_icon(message, '🔒')
                    else:
                        perms.log_unpermitted()
                        await self.respond_with_icon(message, '⛔')
                        if perms.response:
                            try:
                                await message.channel.send(embed=perms.response
                                                           )
                            except (discord.Forbidden, discord.NotFound):
                                pass
                else:
                    await self.respond_with_icon(message, '❄')
            else:
                await self.respond_with_icon(message, '🕙')
예제 #6
0
class SigmaCommand(object):
    def __init__(self, bot, command, plugin_info, command_info):
        self.bot = bot
        self.db = self.bot.db
        self.cd = CommandRateLimiter(self)
        self.command = command
        self.plugin_info = plugin_info
        self.command_info = command_info
        self.name = self.command_info.get('name')
        self.path = self.command_info.get('path')
        self.category = self.plugin_info.get('category')
        self.subcategory = self.plugin_info.get('subcategory')
        self.log = create_logger(self.name.upper())
        self.nsfw = False
        self.cfg = {}
        self.cache = {}
        if self.bot.cfg.pref.raw.get('elastic'):
            self.stats = ElasticHandler(self.bot.cfg.pref.raw.get('elastic'), 'sigma-command')
        else:
            self.stats = None
        self.owner = False
        self.partner = False
        self.dmable = False
        self.requirements = ['send_messages', 'embed_links']
        self.alts = []
        self.usage = '{pfx}{cmd}'
        self.desc = 'No description provided.'
        self.insert_command_info()
        self.load_command_config()

    @staticmethod
    def get_usr_data(usr: discord.User):
        usr_data = {
            'color': str(usr.color) if isinstance(usr, discord.Member) else '#000000',
            'created': str(usr.created_at),
            'discriminator': usr.discriminator,
            'display_name': usr.display_name,
            'game': (usr.activity.name if usr.activity else None) if isinstance(usr, discord.Member) else None,
            'id': usr.id,
            'name': usr.name,
            'status': str(usr.status) if isinstance(usr, discord.Member) else None
        }
        return usr_data

    async def add_elastic_stats(self, message: discord.Message, args: list):
        ath = message.author
        chn = message.channel if message.channel else None
        gld = message.guild if message.guild else None
        ath_data = self.get_usr_data(ath)
        if chn:
            chn_data = {
                'id': chn.id,
                'nsfw': chn.is_nsfw() if not isinstance(chn, discord.DMChannel) else False,
                'name': chn.name if not isinstance(chn, discord.DMChannel) else f'{ath.name}#{ath.discriminator}',
            }
        else:
            chn_data = None
        if gld:
            gld_data = {
                'channels': len(gld.channels),
                'created': str(gld.created_at),
                'id': gld.id,
                'name': gld.name,
                'large': gld.large,
                'members': {
                    'users': len([x for x in gld.members if not x.bot]),
                    'bots': len([x for x in gld.members if x.bot]),
                    'total': len(gld.members)
                },
                'region': str(gld.region),
                'roles': len(gld.roles)
            }
        else:
            gld_data = None
        stat_data = {
            'command': {
                'name': self.name,
                'category': self.category,
                'nsfw': self.nsfw
            },
            'arguments': args,
            'origin': {
                'author': ath_data,
                'channel': chn_data,
                'guild': gld_data
            },
            'time': {
                'executed': {
                    'date': arrow.utcnow().format('YYYY-MM-DD'),
                    'stamp': int(arrow.utcnow().float_timestamp * 1000)
                },
                'created': {
                    'date': arrow.get(message.created_at).format('YYYY-MM-DD'),
                    'stamp': int(arrow.utcnow().float_timestamp * 1000)
                }
            }
        }
        await self.stats.post(stat_data)

    def insert_command_info(self):
        self.alts = self.command_info.get('alts') or []
        self.usage = self.command_info.get('usage') or '{pfx}{cmd}'
        self.desc = self.command_info.get('description') or 'No description provided.'
        self.requirements += self.command_info.get('requirements') or []
        permissions = self.command_info.get('permissions')
        if permissions:
            self.nsfw = bool(permissions.get('nsfw'))
            self.owner = bool(permissions.get('owner'))
            self.partner = bool(permissions.get('partner'))
            self.dmable = bool(permissions.get('dmable'))
        if self.owner:
            self.desc += '\n(Bot Owner Only)'

    def load_command_config(self):
        config_path = f'config/plugins/{self.name}.yml'
        if os.path.exists(config_path):
            with open(config_path) as config_file:
                self.cfg = yaml.safe_load(config_file)

    def resource(self, res_path: str):
        module_path = self.path
        res_path = f'{module_path}/res/{res_path}'
        res_path = res_path.replace('\\', '/')
        return res_path

    def get_exception(self):
        if self.bot.cfg.pref.dev_mode:
            cmd_exception = DummyException
        else:
            cmd_exception = Exception
        return cmd_exception

    def log_command_usage(self, message: discord.Message, args: list):
        if message.guild:
            cmd_location = f'SRV: {message.guild.name} [{message.guild.id}] | '
            cmd_location += f'CHN: #{message.channel.name} [{message.channel.id}]'
        else:
            cmd_location = 'DIRECT MESSAGE'
        author_full = f'{message.author.name}#{message.author.discriminator} [{message.author.id}]'
        log_text = f'USR: {author_full} | {cmd_location}'
        if args:
            log_text += f' | ARGS: {" ".join(args)}'
        self.log.info(log_text)

    async def add_usage_exp(self, message: discord.Message):
        if message.guild:
            if not await self.bot.cool_down.on_cooldown('UsageExperience', message.author):
                award_xp = (600 if message.guild.large else 500) + secrets.randbelow(100)
                await self.db.add_experience(message.author, message.guild, award_xp)
                await self.bot.cool_down.set_cooldown('UsageExperience', message.author, 450)

    @staticmethod
    async def respond_with_icon(message: discord.Message, icon: str or discord.Emoji):
        try:
            await message.add_reaction(icon)
        except discord.DiscordException:
            pass

    async def log_error(self, message: discord.Message, args: list, exception: Exception, error_token: str):
        if message.guild:
            gnam = message.guild.name
            gid = message.guild.id
            cnam = message.channel.name
            cid = message.channel.id
        else:
            gnam = None
            gid = None
            cnam = None
            cid = None
        err_file_data = {
            'Token': error_token,
            'Error': f'{exception}',
            'TraceBack': {
                'Class': f'{exception.with_traceback}',
                'Details': traceback.format_exc()
            },
            'Message': {
                'Command': self.name,
                'Arguments': args,
                'ID': message.id
            },
            'Author': {
                'Name': f'{message.author.name}#{message.author.discriminator}',
                'ID': message.author.id
            },
            'Guild': {
                'Name': gnam,
                'ID': gid
            },
            'Channel': {
                'Name': cnam,
                'ID': cid
            }
        }
        await self.db[self.bot.cfg.db.database].Errors.insert_one(err_file_data)
        log_text = f'ERROR: {exception} | TOKEN: {error_token} | TRACE: {exception.with_traceback}'
        self.log.error(log_text)

    async def execute(self, message: discord.Message, args: list):
        if self.bot.ready:
            if message.guild:
                delete_command_message = await self.db.get_guild_settings(message.guild.id, 'DeleteCommands')
                if delete_command_message:
                    try:
                        await message.delete()
                    except discord.Forbidden:
                        pass
                    except discord.NotFound:
                        pass
            if not self.bot.cfg.dsc.bot and message.author.id != self.bot.user.id:
                self.log.warning(f'{message.author.name} tried using me.')
                return
            if not self.cd.is_cooling(message):
                perms = GlobalCommandPermissions(self, message)
                await perms.check_black_usr()
                await perms.check_black_srv()
                await perms.generate_response()
                perms.check_final()
                guild_allowed = ServerCommandPermissions(self, message)
                await guild_allowed.check_perms()
                self.log_command_usage(message, args)
                self.cd.set_cooling(message)
                if perms.permitted:
                    if guild_allowed.permitted:
                        requirements = CommandRequirements(self, message)
                        if requirements.reqs_met:
                            try:
                                await getattr(self.command, self.name)(self, message, args)
                                await add_cmd_stat(self)
                                if self.stats:
                                    await self.add_elastic_stats(message, args)
                                await self.add_usage_exp(message)
                                self.bot.command_count += 1
                                self.bot.loop.create_task(self.bot.queue.event_runner('command', self, message, args))
                            except self.get_exception() as e:
                                await self.respond_with_icon(message, '❗')
                                err_token = secrets.token_hex(16)
                                await self.log_error(message, args, e, err_token)
                                prefix = await self.db.get_prefix(message)
                                title = '❗ An Error Occurred!'
                                err_text = 'Something seems to have gone wrong.'
                                err_text += '\nPlease send this token to our support server.'
                                err_text += f'\nThe invite link is in the **{prefix}help** command.'
                                err_text += f'\nToken: **{err_token}**'
                                error_embed = discord.Embed(color=0xBE1931)
                                error_embed.add_field(name=title, value=err_text)
                                try:
                                    await message.channel.send(embed=error_embed)
                                except discord.Forbidden:
                                    pass
                        else:
                            await self.respond_with_icon(message, '❗')
                            reqs_embed = discord.Embed(color=0xBE1931)
                            reqs_error_title = f'❗ Sigma is missing permissions!'
                            reqs_error_list = ''
                            for req in requirements.missing_list:
                                req = req.replace('_', ' ').title()
                                reqs_error_list += f'\n- {req}'
                            prefix = await self.db.get_prefix(message)
                            reqs_embed.add_field(name=reqs_error_title, value=f'```\n{reqs_error_list}\n```')
                            reqs_embed.set_footer(text=f'{prefix}{self.name} could not execute.')
                            try:
                                await message.channel.send(embed=reqs_embed)
                            except discord.Forbidden:
                                pass
                    else:
                        self.log.warning('ACCESS DENIED: This module or command is not allowed in this location.')
                        await self.respond_with_icon(message, '⛔')
                else:
                    perms.log_unpermitted()
                    await self.respond_with_icon(message, '⛔')
                    if perms.response:
                        try:
                            await message.channel.send(embed=perms.response)
                        except discord.Forbidden:
                            pass
            else:
                await self.respond_with_icon(message, '🕙')
예제 #7
0
class SigmaCommand(object):
    def __init__(self, bot, command, plugin_info, command_info):
        self.bot = bot
        self.db = self.bot.db
        self.cd = CommandRateLimiter(self)
        self.command = command
        self.plugin_info = plugin_info
        self.command_info = command_info
        self.name = self.command_info.get('name')
        self.path = self.command_info.get('path')
        self.category = self.plugin_info.get('category')
        self.subcategory = self.plugin_info.get('subcategory')
        self.log = create_logger(self.name.upper())
        self.nsfw = False
        self.cfg = {}
        self.cache = {}
        self.owner = False
        self.partner = False
        self.dmable = False
        self.requirements = ['send_messages', 'embed_links']
        self.alts = []
        self.usage = '{pfx}{cmd}'
        self.desc = 'No description provided.'
        self.insert_command_info()
        self.load_command_config()

    @staticmethod
    def get_usr_data(usr: discord.User):
        usr_data = {
            'color':
            str(usr.color) if isinstance(usr, discord.Member) else '#000000',
            'created':
            str(usr.created_at),
            'discriminator':
            usr.discriminator,
            'display_name':
            usr.display_name,
            'game': (usr.activity.name if usr.activity else None)
            if isinstance(usr, discord.Member) else None,
            'id':
            usr.id,
            'name':
            usr.name,
            'status':
            str(usr.status) if isinstance(usr, discord.Member) else None
        }
        return usr_data

    def insert_command_info(self):
        self.alts = self.command_info.get('alts', [])
        self.usage = self.command_info.get('usage', '{pfx}{cmd}')
        self.desc = self.command_info.get('description',
                                          'No description provided.')
        self.requirements += self.command_info.get('requirements', [])
        permissions = self.command_info.get('permissions', {})
        if permissions:
            self.nsfw = bool(permissions.get('nsfw'))
            self.owner = bool(permissions.get('owner'))
            self.partner = bool(permissions.get('partner'))
            self.dmable = bool(permissions.get('dmable'))
        if self.owner:
            self.desc += '\n(Bot Owner Only)'

    def load_command_config(self):
        config_path = f'config/plugins/{self.name}.yml'
        if os.path.exists(config_path):
            with open(config_path) as config_file:
                self.cfg = yaml.safe_load(config_file)

    def resource(self, res_path: str):
        module_path = self.path
        res_path = f'{module_path}/res/{res_path}'
        res_path = res_path.replace('\\', '/')
        return res_path

    def get_exception(self):
        if self.bot.cfg.pref.dev_mode:
            cmd_exception = DummyException
        else:
            cmd_exception = Exception
        return cmd_exception

    def log_command_usage(self, message: discord.Message, args: list,
                          extime: int):
        crst = arrow.get(message.created_at).float_timestamp
        exdiff = round(extime - crst, 3)
        if message.guild:
            cmd_location = f'SRV: {message.guild.name} [{message.guild.id}] | '
            cmd_location += f'CHN: #{message.channel.name} [{message.channel.id}]'
        else:
            cmd_location = 'DIRECT MESSAGE'
        author_full = f'{message.author.name}#{message.author.discriminator} [{message.author.id}]'
        log_text = f'USR: {author_full} | {cmd_location}'
        if args:
            log_text += f' | ARGS: {" ".join(args)}'
        log_text += f' | EX: {exdiff}'
        self.log.info(log_text)

    async def add_usage_exp(self, message: discord.Message):
        if message.guild:
            if not await self.bot.cool_down.on_cooldown(
                    'UsageExperience', message.author):
                award_xp = (600 if message.guild.large else
                            500) + secrets.randbelow(100)
                await self.db.add_experience(message.author, message.guild,
                                             award_xp)
                await self.bot.cool_down.set_cooldown('UsageExperience',
                                                      message.author, 450)

    @staticmethod
    async def respond_with_icon(message: discord.Message, icon: str
                                or discord.Emoji):
        try:
            await message.add_reaction(icon)
        except discord.DiscordException:
            pass

    async def log_error(self, message: discord.Message, args: list,
                        exception: Exception, error_token: str):
        if message.guild:
            gnam = message.guild.name
            gid = message.guild.id
            cnam = message.channel.name
            cid = message.channel.id
        else:
            gnam = None
            gid = None
            cnam = None
            cid = None
        err_file_data = {
            'Token': error_token,
            'Error': f'{exception}',
            'TraceBack': {
                'Class': f'{exception.with_traceback}',
                'Details': traceback.format_exc()
            },
            'Message': {
                'Command': self.name,
                'Arguments': args,
                'ID': message.id
            },
            'Author': {
                'Name':
                f'{message.author.name}#{message.author.discriminator}',
                'ID': message.author.id
            },
            'Guild': {
                'Name': gnam,
                'ID': gid
            },
            'Channel': {
                'Name': cnam,
                'ID': cid
            }
        }
        if self.bot.cfg.pref.errorlog_channel:
            err_chn_id = self.bot.cfg.pref.errorlog_channel
            error_chn = discord.utils.find(lambda x: x.id == err_chn_id,
                                           self.bot.get_all_channels())
            await send_error_embed(error_chn, err_file_data)
        await self.db[self.bot.cfg.db.database
                      ].Errors.insert_one(err_file_data)
        log_text = f'ERROR: {exception} | TOKEN: {error_token} | TRACE: {exception.with_traceback}'
        self.log.error(log_text)

    async def send_error_message(self, message: discord.Message, args: list,
                                 e: Exception):
        await self.respond_with_icon(message, '❗')
        err_token = secrets.token_hex(16)
        await self.log_error(message, args, e, err_token)
        prefix = await self.db.get_prefix(message)
        name = self.bot.user.name
        if isinstance(e, discord.Forbidden):
            title = '❗ Error: Forbidden!'
            err_text = f'It seems that you tried running something that {name} isn\'t allowed to do.'
            err_text += f' This is something when {name} is missing permissions for stuff like'
            err_text += ' sending messages, adding reactions, uploading files, etc.'
            err_text += ' The error has been relayed to the developers. If you feel like dropping by'
            err_text += f' and asking about it, the invite link is in the **{prefix}help** command.'
        elif isinstance(e, discord.NotFound):
            title = '❗ Error: Not Found!'
            err_text = 'It might have been a target that got removed while the command was executing,'
            err_text += f' whatever it was, {name} couldn\'t find it and encountered an error.'
            err_text += ' The error has been relayed to the developers. If you feel like dropping by'
            err_text += f' and asking about it, the invite link is in the **{prefix}help** command.'
        else:
            title = '❗ An Unhandled Error Occurred!'
            err_text = 'Something seems to have gone wrong.'
            err_text += '\nPlease be patient while we work on fixing the issue.'
            err_text += '\nThe error has been relayed to the developers.'
            err_text += f'\nIf you feel like dropping by and asking about it,'
            err_text += f'\nthe invite link is in the **{prefix}help** command.'
        error_embed = discord.Embed(color=0xBE1931)
        error_embed.add_field(name=title, value=err_text)
        error_embed.set_footer(text=f'Token: {err_token}')
        try:
            await message.channel.send(embed=error_embed)
        except discord.Forbidden:
            pass

    async def update_cooldown(self, author):
        cdfile = await self.db[self.db.db_nam].CommandCooldowns.find_one(
            {'Command': self.name}) or {}
        cooldown = cdfile.get('Cooldown')
        if cooldown:
            await self.bot.cool_down.set_cooldown(f'{self.name}_core', author,
                                                  cooldown)

    async def execute(self, message: discord.Message, args: list):
        if self.bot.ready:
            if message.guild:
                delete_command_message = await self.db.get_guild_settings(
                    message.guild.id, 'DeleteCommands')
                if delete_command_message:
                    try:
                        await message.delete()
                    except discord.Forbidden:
                        pass
                    except discord.NotFound:
                        pass
            if not self.bot.cfg.dsc.bot and message.author.id != self.bot.user.id:
                self.log.warning(f'{message.author.name} tried using me.')
                return
            if not self.cd.is_cooling(message):
                if not await self.bot.cool_down.on_cooldown(
                        f'{self.name}_core', message.author):
                    await self.update_cooldown(message.author)
                    perms = GlobalCommandPermissions(self, message)
                    await perms.check_black_usr()
                    await perms.check_black_srv()
                    await perms.generate_response()
                    perms.check_final()
                    guild_allowed = ServerCommandPermissions(self, message)
                    await guild_allowed.check_perms()
                    self.log_command_usage(message, args,
                                           arrow.utcnow().float_timestamp)
                    self.cd.set_cooling(message)
                    if perms.permitted:
                        if guild_allowed.permitted:
                            requirements = CommandRequirements(self, message)
                            if requirements.reqs_met:
                                try:
                                    await getattr(self.command,
                                                  self.name)(self, message,
                                                             args)
                                    await add_cmd_stat(self)
                                    await self.add_usage_exp(message)
                                    self.bot.command_count += 1
                                    event_task = self.bot.queue.event_runner(
                                        'command', self, message, args)
                                    self.bot.loop.create_task(event_task)
                                except self.get_exception() as e:
                                    await self.send_error_message(
                                        message, args, e)

                            else:
                                await self.respond_with_icon(message, '❗')
                                reqs_embed = discord.Embed(color=0xBE1931)
                                reqs_error_title = f'❗ {self.bot.user.name} is missing permissions!'
                                reqs_error_list = ''
                                for req in requirements.missing_list:
                                    req = req.replace('_', ' ').title()
                                    reqs_error_list += f'\n- {req}'
                                prefix = await self.db.get_prefix(message)
                                reqs_embed.add_field(
                                    name=reqs_error_title,
                                    value=f'```\n{reqs_error_list}\n```')
                                reqs_embed.set_footer(
                                    text=
                                    f'{prefix}{self.name} could not execute.')
                                try:
                                    await message.channel.send(embed=reqs_embed
                                                               )
                                except discord.Forbidden:
                                    pass
                        else:
                            self.log.warning(
                                'ACCESS DENIED: This module or command is not allowed in this location.'
                            )
                            await self.respond_with_icon(message, '⛔')
                    else:
                        perms.log_unpermitted()
                        await self.respond_with_icon(message, '⛔')
                        if perms.response:
                            try:
                                await message.channel.send(embed=perms.response
                                                           )
                            except discord.Forbidden:
                                pass
                else:
                    await self.respond_with_icon(message, '❄')
            else:
                await self.respond_with_icon(message, '🕙')