Exemplo n.º 1
0
class Plugin(Logger):
    id = "plugin"
    name = "Plugin"
    description = ""

    is_global = False

    db = redis.from_url(os.getenv('REDIS_URL'), decode_responses=True)

    guild_storages = {}

    def to_dict(self, guild_id=None):
        dct = {
            'id': self.id,
            'name': self.name,
            'description': self.description,
            'commands': [cmd.to_dict(guild_id) for cmd in self.commands]
        }

        if guild_id is not None:
            dct['enabled'] = self.check_guild(guild_id)
            dct['config'] = self.get_config(guild_id)

        return dct

    def __init__(self, in_bot=True):
        self.plugin_db = PrefixedRedis(self.db, 'plugin.' + self.id + '.')

        self.in_bot = in_bot

        methods = inspect.getmembers(self, predicate=inspect.ismethod)
        commands_callbacks = [
            meth for name, meth in methods if get(meth, 'command_info')
        ]
        self.commands = []
        for cb in commands_callbacks:
            info = cb.command_info

            opts = dict()
            opts['expression'] = info['expression']
            opts['description'] = info['description']
            opts['callback'] = cb
            opts['plugin'] = self
            opts['restrict_default'] = info.get('restrict_default', False)

            command = Command(**opts)
            self.commands.append(command)

        # Configs group keys
        self.config_db = GroupKeys(self.id + '.config',
                                   self.plugin_db,
                                   cache=in_bot,
                                   callback=self.handle_config_change)

        # Commands Configs group keys
        self.commands_config_db = GroupKeys(
            self.id + '.cmd-config',
            self.plugin_db,
            cache=in_bot,
            callback=self.handle_commands_config_change)

    def on_config_change(self, guild, config):
        pass

    def on_message_create(self, guild, message):
        for command in self.commands:
            command.execute(guild, message)

    def on_guild_join(self, guild):
        pass

    def on_guild_leave(self, guild):
        pass

    def on_member_join(self, guild, member):
        pass

    def on_member_leave(self, guild, member):
        pass

    def get_guild_storage(self, guild):
        guild_id = get(guild, 'id', guild)
        guild_storage = self.guild_storages.get(guild_id)

        if guild_storage:
            return guild_storage

        prefix = 'plugin.{}.guild.{}.storage.'.format(self.id, guild_id)
        guild_storage = PrefixedRedis(self.db, prefix)
        self.guild_storages[guild_id] = guild_storage

        return guild_storage

    def get_guilds(self):
        guilds = self.db.smembers('plugin.{}.guilds'.format(self.name))
        guilds = [
            guild for guild in guilds if self.db.sismember('servers', guild)
        ]

        return [self._make_guild({'id': id}) for id in guilds]

    def enable(self, guild):
        guild_id = get(guild, 'id', guild)
        self.db.sadd('plugins:{}'.format(guild_id), self.name)
        self.db.sadd('plugin.{}.guilds'.format(self.name), guild_id)

    def disable(self, guild):
        guild_id = get(guild, 'id', guild)
        self.db.srem('plugins:{}'.format(guild_id), self.name)
        self.db.srem('plugin.{}.guilds'.format(self.name), guild_id)

    def check_guild(self, guild):
        guild_id = get(guild, 'id', guild)

        if not self.db.sismember('servers', guild_id):
            return False

        plugins = self.db.smembers('plugins:{}'.format(guild_id))
        return self.name in plugins

    def _make_guild(self, guild_payload):
        guild = Guild(**guild_payload)
        guild.db = self.db
        guild.plugin = self
        return guild

    def handle_commands_config_change(self, payload):
        pass

    def handle_config_change(self, payload):
        op = payload[0]
        if op == 's':
            key = payload[1]
            value = payload[2]

            guild_id = int(key.split('.')[-1])
            config = json.loads(value)
            guild = self._make_guild({'id': guild_id})

            self.on_config_change(guild, config)

    def get_config(self, guild):
        guild_id = get(guild, 'id', guild)

        key = 'config.{}'.format(guild_id)
        raw_config = self.config_db.get(key)

        if raw_config:
            config = self.config_model(**json.loads(raw_config))
        else:
            config = self.get_default_config(guild_id)

        return config

    def get_default_config(self, guild_id):
        raise NotImplemented

    def patch_config(self, guild, raw_config):
        guild_id = get(guild, 'id', guild)
        config = self.get_config(guild_id)
        new_config = self.config_model(**raw_config)
        new_config.sanitize()
        new_config.validate()
        # pre-hook
        self.before_config_patch(guild_id, config, new_config)
        self.config_db.set('config.{}'.format(guild_id),
                           json.dumps(new_config.serialize()))
        # post-hook
        self.after_config_patch(guild_id, new_config)
        return new_config

    def patch_config_old(self, guild, partial_new_config):
        guild_id = get(guild, 'id', guild)
        config = self.get_config(guild_id)
        new_config = deepcopy(config)
        for field_name, field in config.__class__._fields.items():
            new_value = partial_new_config.get(field_name)
            if new_value is not None:
                setattr(new_config, field_name, field.deserialize(new_value))
        new_config.sanitize()
        new_config.validate()
        # pre-hook
        self.before_config_patch(guild_id, config, new_config)
        self.config_db.set('config.{}'.format(guild_id),
                           json.dumps(new_config.serialize()))
        # post-hook
        self.after_config_patch(guild_id, new_config)
        return new_config

    def before_config_patch(self, guild_id, old_config, new_config):
        pass

    def after_config_patch(self, guild_id, config):
        pass

    def handle_event(self, payload):
        event_type = payload['t']
        guild_id = payload['g']
        data = payload.get('d')

        if data:
            data_type_name = event_type.split('_')[0].lower()
            data_type_module = get(mee6.types, data_type_name)
            if data_type_module:
                data_type = get(data_type_module, data_type_name.capitalize())
            else:
                data_type = None

            if data_type:
                decoded_data = data_type(**data)
            else:
                decoded_data = data

        listener = get(self, 'on_' + event_type.lower())
        if listener:
            if data:
                gevent.spawn(listener, guild_id, decoded_data)
            else:
                gevent.spawn(listener, guild_id)

    @classmethod
    def loop(cls, sleep_time=1):
        def deco(f):
            f.is_loop = 1
            f.sleep_time = 1
            return f

        return deco

    def get_loop_container(self, loop):
        def loop_container():
            import traceback
            while True:
                try:
                    loop()
                except Exception as e:
                    traceback.print_exc()
                    gevent.sleep(3)

                gevent.sleep(loop.sleep_time)

        return loop_container

    def run(self):
        loops = []
        for name, method in self.__class__.__dict__.items():
            if hasattr(method, 'is_loop'):
                loops.append(name)

        loops = [get(self, loop) for loop in loops]

        active_loops = []

        import copy
        for loop in loops:
            active_loops.append(gevent.spawn(self.get_loop_container(loop)))

        gevent.joinall(active_loops)
Exemplo n.º 2
0
class Command:
    @classmethod
    def register(cls, expression):
        def deco(f):
            command_info = {
                'expression': expression,
                'callback': f,
                'description': ''
            }
            f.command_info = command_info
            return f

        return deco

    @classmethod
    def description(cls, description):
        def deco(f):
            f.command_info['description'] = description
            return f

        return deco

    @classmethod
    def restrict_default(cls, f):
        f.command_info['restrict_default'] = True
        return f

    def to_dict(self, guild_id=None):
        dct = {
            'id': self.id,
            'name': self.name,
            'description': self.description,
            '_expression': self.expression
        }

        if guild_id is not None:
            dct['config'] = self.get_config(guild_id).serialize()

        return dct

    def __init__(self,
                 expression=None,
                 callback=None,
                 require_roles=[],
                 description="",
                 after_check=lambda _, __: True,
                 plugin=None,
                 restrict_default=False):
        self.name = callback.__name__
        self.id = 'command.{}.{}'.format(plugin.id, self.name)
        self.expression = expression
        self.callback = callback
        self.require_roles = require_roles
        self.description = description
        self.regex, self.cast_to = build_regex(self.expression)
        self.after_check = after_check
        self.restrict_default = restrict_default

        self.plugin = plugin

        self.command_db = PrefixedRedis(plugin.db, self.id + '.')
        self.config_db = GroupKeys(self.id + '.config',
                                   self.command_db,
                                   cache=plugin.in_bot)

    def default_config(self, guild):
        guild_id = get(guild, 'id', guild)

        default_config = {
            'allowed_roles': [],
            'enabled': True,
            'global_cooldown': -1,
            'personal_cooldown': -1
        }

        if not self.restrict_default:
            default_config['allowed_roles'] = [guild_id]

        return CommandConfig(**default_config)

    def get_config(self, guild):
        guild_id = get(guild, 'id', guild)

        raw_config = self.config_db.get('config.{}'.format(guild_id))
        if raw_config is None:
            return self.default_config(guild)

        config = json.loads(raw_config)
        return CommandConfig(**config)

    def patch_config(self, guild, partial_new_config):
        guild_id = get(guild, 'id', guild)
        config = self.get_config(guild)
        for field_name in config.__class__._fields:
            new_value = partial_new_config.get(field_name)
            if new_value is not None:
                setattr(config, field_name, new_value)
        config.sanitize()
        config.validate()
        raw_config = json.dumps(config.serialize())
        self.config_db.set('config.{}'.format(guild_id), raw_config)
        return config

    def delete_config(self, guild):
        guild_id = get(guild, 'id', guild)
        self.config_db.delete('config.{}'.format(guild_id))

    def check_permission(self, ctx):
        member_permissions = ctx.author.guild_permissions

        if (member_permissions >> 5 & 1) or (member_permissions >> 3 & 1):
            return True

        if int(ctx.author.id) == int(ctx.guild.owner_id):
            return True

        config = self.get_config(ctx.guild)
        allowed_roles = config.allowed_roles
        for role in ctx.author.roles:
            role_id = get(role, 'id', role)
            if role_id in allowed_roles:
                return True

        return False

    def check_enabled(self, ctx):
        config = self.get_config(ctx.guild)
        return config.enabled

    def check_cooldown(self, ctx):
        config = self.get_config(ctx.guild)

        global_cooldown = config.global_cooldown
        if global_cooldown > -1:
            key = 'cooldown.{}'.format(ctx.guild.id)
            cd_check = self.command_db.get(key)
            if cb_check:
                return False

            self.command_db.setex(key, 1, global_cooldown)

        personal_cooldown = config.personal_cooldown
        if global_cooldown > -1:
            key = 'cooldown.{}.{}'.format(ctx.guild.id, ctx.author.id)
            cd_check = self.command_db.get(key)
            if cb_check:
                return False

            self.command_db.setex(key, 1, personal_cooldown)

        return True

    def check_match(self, msg):
        match = self.regex.match(msg)
        if not match:
            return None

        return CommandMatch(self, match)

    def execute(self, guild, message):
        match = self.check_match(message.content)
        if match is None:
            return

        ctx = CommandContext(guild, message)

        if not self.check_permission(ctx):
            return

        if not self.check_enabled(ctx):
            return

        if not self.check_cooldown(ctx):
            return

        if not self.after_check(self, ctx):
            return

        try:
            response = self.callback(ctx, *match.arguments)
        except Exception as e:
            response = Response.internal_error()
            traceback.print_exc()

        if response:
            return response.send(guild, message.channel_id)