Example #1
0
class RoleByReaction(Cog):

    ######################################### CONSTRUCTOR #########################################

    def __init__(self, bot: Bot):
        self.bot = bot
        self.config = Cfg(self)

        self.defaults = GuildData(
            channel=0,
            combinations=list(),
            message=0,
            title="React with corresponding emoji to get role"
        )
        self.config.defaults_guild(self.defaults)

        self.bot.loop.create_task(self.startup_check())

    ########################################### UNLOADER ##########################################

    def cog_unload(self):
        del self

    ########################################## SCHEDULER ##########################################

    async def startup_check(self):
        await self.bot.wait_until_ready()

        guilds_configs = self.config.all_guilds()
        for guild_id, guild_config in guilds_configs.items():
            guild = self.bot.get_guild(guild_id)
            if guild:
                guild_data = guild_config.get()
                await self._treat_guild(guild, guild_data)

    ########################################### EVENTS ############################################

    async def _treat_payload(self, payload: RawReactionActionEvent):
        guild = self.bot.get_guild(payload.guild_id)
        if guild:
            guild_config = self.config.guild(guild)
            guild_data = guild_config.get()
            await self._treat_reaction(guild, guild_data, payload)

    @Cog.listener()
    async def on_raw_reaction_add(self, payload: RawReactionActionEvent):
        await self._treat_payload(payload)

    @Cog.listener()
    async def on_raw_reaction_remove(self, payload: RawReactionActionEvent):
        await self._treat_payload(payload)

    ################################## ROLE BY REACTION COMMANDS ##################################

    @admin_or_permissions()
    @group()
    async def rbr(self, ctx: Context):
        pass

    @admin()
    @rbr.command()
    async def title(self, ctx: Context, *, title: str):
        guild_config = self.config.guild(ctx.guild)
        guild_data = guild_config.get()

        guild_data.title = title
        try:
            await self._edit_rbr_message(ctx, guild_data)
        except InvalidArguments:
            pass

        guild_config.set(guild_data)

        embed = Embed(
            title='Title Changed',
            description=f'Successfully updated title to {title}'
        )
        await ctx.send(embed=embed)

    @admin_or_permissions(manage_roles=True)
    @rbr.command()
    async def add(self, ctx: Context, emoji: EmojiType, *, role: RoleType):
        guild = ctx.guild
        guild_config = self.config.guild(guild)
        guild_data = guild_config.get()

        combinations = guild_data.combinations

        try:
            emoji = await self.import_emoji(ctx, emoji)
            if emoji in [c.emoji for c in combinations]:
                raise InvalidArguments(
                    ctx=ctx,
                    title="Role Error",
                    message=f"Role {role} already used"
                )

            role = await self.import_role(ctx, role)
            if role.id in [c.role for c in combinations]:
                raise InvalidArguments(
                    ctx=ctx,
                    title="Role Error",
                    message=f"Role {role} already used"
                )

            if not can_give_role(role, ctx.me):
                raise InvalidArguments(
                    ctx=ctx,
                    title="Role Error",
                    message=f"Bot doesn't have enough rights to give role"
                )
        except InvalidArguments as error:
            await error.execute()

        else:
            new = Combination(
                emoji=emoji.id if hasattr(emoji, "id") else emoji,
                role=role.id
            )
            combinations.append(new)

            guild_data.combinations = combinations
            try:
                await self._edit_rbr_message(ctx, guild_data)
            except InvalidArguments:
                pass

            guild_config.set(guild_data)

            embed = Embed(
                title="Combination Added",
                description=f"{emoji} successfully linked with {role}"
            )
            await ctx.send(embed=embed)

    @admin_or_permissions(manage_roles=True)
    @rbr.command()
    async def remove(self, ctx: Context, *, element: Union[EmojiType, RoleType]):
        guild = ctx.guild
        guild_config = self.config.guild(guild)
        guild_data = guild_config.get()

        combinations = guild_data.combinations

        try:
            if not combinations:
                raise InvalidArguments(
                    ctx=ctx,
                    title="Data Error",
                    message="No data registered yet"
                )

            try:
                element = await self.import_emoji(ctx, element)
                element = element.id if hasattr(element, "id") else element
                var = "emoji"
            except InvalidArguments:
                try:
                    role = await self.import_role(ctx, element)
                    element = role.id
                    var = "role"
                except InvalidArguments:
                    raise InvalidArguments(
                        ctx=ctx,
                        message="Reference not found"
                    )

            if element not in [c[var] for c in combinations]:
                raise InvalidArguments(
                    ctx=ctx,
                    message="Argument wasn't found in data"
                )
        except InvalidArguments as error:
            await error.execute()

        else:
            for combination in combinations:
                if combination[var] == element:
                    combinations.remove(combination)
                    break

            guild_data.combinations = combinations
            try:
                await self._edit_rbr_message(ctx, guild_data)
            except InvalidArguments:
                pass

            guild_config.set(guild_data)

            emoji = await self.import_emoji(ctx, combination.emoji)
            role = guild.get_role(combination.role)

            embed = Embed(
                title="Combination Removed",
                description=f"{emoji} and {role} successfully unlinked"
            )
            await ctx.send(embed=embed)

    @admin()
    @rbr.command()
    async def create(self, ctx: Context):
        guild = ctx.guild
        guild_config = self.config.guild(guild)
        guild_data = guild_config.get()

        embed = self._rbr_message_content(guild, guild_data)
        message = await ctx.send(embed=embed)
        await self._add_reactions(
            ctx=ctx,
            message=message,
            emojis=[c.emoji for c in guild_data.combinations]
        )

        guild_data.channel = message.channel.id
        guild_data.message = message.id
        guild_config.set(guild_data)

    @admin()
    @rbr.command()
    async def message(self, ctx: Context, channel_id: int, message_id: int):
        guild = ctx.guild
        guild_config = self.config.guild(guild)
        guild_data = guild_config.get()

        guild_data.channel = channel_id
        guild_data.message = message_id
        try:
            message = await self._edit_rbr_message(ctx, guild_data)
        except InvalidArguments as error:
            await error.execute()

        else:
            guild_config.set(guild_data)

            embed = Embed(
                title="Message Set",
                description=f"[jump to]({message.jump_url})"
            )
            await ctx.send(embed=embed)

    @admin_or_permissions(manage_roles=True)
    @rbr.command()
    async def show(self, ctx: Context):
        guild = ctx.guild
        guild_config = self.config.guild(guild)
        guild_data = guild_config.get()

        guild_data.title = "Role By Reaction Template"

        embed = self._rbr_message_content(guild, guild_data)
        await ctx.send(embed=embed)

    @admin()
    @rbr.command()
    async def reset(self, ctx: Context):
        answer = await ask_confirmation(ctx)
        if answer:
            guild = ctx.guild
            self.config.guild(guild).set(self.defaults)

            embed = Embed(
                title="Data Reset"
            )
            await ctx.send(embed=embed)

        else:
            embed = Embed(
                title="Cancelled"
            )
            await ctx.send(embed=embed)

    @admin()
    @rbr.command()
    async def update(self, ctx: Context):
        guild = ctx.guild
        guild_config = self.config.guild(guild)
        guild_data = guild_config.get()

        try:
            await self._edit_rbr_message(ctx, guild_data)
        except InvalidArguments as error:
            await error.execute()

    ######################################## CLASS METHODS ########################################

    @classmethod
    async def _add_reactions(cls, ctx: Context, message: Message, emojis: List[Union[int, str]]):
        for emoji in emojis:
            try:
                emoji = ImprovedList(ctx.guild.emojis).get_item(
                    emoji,
                    key=lambda e: e.id
                )
                await message.add_reaction(emoji)
            except ValueError:
                try:
                    await message.add_reaction(str(emoji))
                except:
                    print(f"ROLEBYREACTION_COG: couldn't react with {emoji} on {message.jump_url}")

    @classmethod
    async def _edit_rbr_message(cls, ctx: Context, guild_data: GuildData) -> Message:
        guild = ctx.guild
        channel_id = guild_data.channel
        message_id = guild_data.message
        combinations = guild_data.combinations

        message = await cls._find_rbr_message(guild, guild_data, ctx)
        if message.author.id == ctx.me.id:
            embed = cls._rbr_message_content(guild, guild_data)
            await message.edit(embed=embed)
            await cls._add_reactions(
                ctx=ctx,
                message=message,
                emojis=[c.emoji for c in combinations]
            )
            return message

        else:
            raise InvalidArguments(
                ctx=ctx,
                title="Message Error",
                description="Linked message isn't from bot"
            )

    @classmethod
    async def _treat_guild(cls, guild: Guild, guild_data: GuildData):
        try:
            message = await cls._find_rbr_message(guild, guild_data)
        except InvalidArguments: # message not found
            pass

        else:
            reactions_state = await cls._make_reactions_state(guild, guild_data, message.reactions)
            members_state = cls._make_members_state(guild, guild_data)

            to_add, to_remove = cls._compare_reaction_members(reactions_state, members_state)
            await cls._edit_members_roles(to_add, to_remove)

    @classmethod
    async def _treat_reaction(
        cls, guild: Guild, guild_data: GuildData, payload: RawReactionActionEvent
    ):
        message_id = guild_data.message
        if payload.message_id == message_id:
            combinations = guild_data.combinations

            emoji = payload.emoji
            emoji = emoji.id if emoji.id else str(emoji)

            role = None
            for c in combinations:
                if c.emoji == emoji:
                    role = guild.get_role(c.role)
                    break

            if role:
                member = guild.get_member(payload.user_id)
                if member:
                    method = cls._get_role_method(
                        member=member,
                        event_type=payload.event_type
                    )
                    await method(role)

    ####################################### STATIC METHODS ########################################

    @staticmethod
    def _compare_reaction_members(
        react_state: Dict[Member, Set[Role]], memb_state: Dict[Member, Set[Role]]
    ) -> Dict[Member, Set[Role]] and Dict[Member, Set[Role]]:
        # Dict[Member, Set[Role]]
        add = dict()
        for member in react_state.keys():
            member_add = react_state[member] - memb_state.get(member, set())
            if member_add:
                add[member] = member_add

        # Dict[Member, Set[Role]]
        remove = dict()
        for member in memb_state.keys():
            member_remove = memb_state[member] - react_state.get(member, set())
            if member_remove:
                remove[member] = member_remove

        return add, remove

    @staticmethod
    async def _edit_members_roles(add: Dict[Member, Set[Role]], remove: Dict[Member, Set[Role]]):
        for member, roles in add.items():
            await member.add_roles(*roles)

        for member, roles in remove.items():
            await member.remove_roles(*roles)

    @staticmethod
    async def _find_rbr_message(guild: Guild, guild_data: GuildData, ctx: Context=None) -> Message:
        channel_id = guild_data.channel
        message_id = guild_data.message

        channel = guild.get_channel(channel_id)
        if channel:
            try:
                return await channel.fetch_message(message_id)
            except NotFound:
                raise InvalidArguments(
                    ctx=ctx,
                    title="Message Not Found",
                    message="Message doesn't exist or not provided"
                )
        else:
            raise InvalidArguments(
                ctx=ctx,
                title="Channel Not Found",
                message="Channel doesn't exist or not provided"
            )

    @staticmethod
    def _get_role_method(member: Member, event_type: str) -> Awaitable:
        if event_type == "REACTION_ADD":
            async def method(role):
                await member.add_roles(role)

        elif event_type == "REACTION_REMOVE":
            async def method(role):
                await member.remove_roles(role)

        else:
            async def method(role):
                return

        return method

    @staticmethod
    def _make_members_state(guild: Guild, guild_data: GuildData) -> Dict[Member, Set[Role]]:
        members = guild.members
        roles = [c.role for c in guild_data.combinations]

        return {member: {role for role in member.roles if role.id in roles} for member in members}

    @staticmethod
    async def _make_reactions_state(
        guild: Guild, guild_data: GuildData, reactions: List[Reaction]
    ) -> Dict[Member, Set[Role]]:
        combinations = guild_data.combinations

        # Dict[Role, Set[Member]]
        state_by_role = dict()
        for reaction in reactions:
            emoji = reaction.emoji
            try:
                combination = ImprovedList(combinations).get_item(
                    v=emoji.id if hasattr(emoji, "id") else str(emoji),
                    key=lambda c: c.emoji
                )
            except ValueError: # emoji not registered in combinations list
                continue

            else:
                role = guild.get_role(combination.role)
                if role: # role might have been deleted, so excluding None case
                    # Set[Member]
                    members = set()
                    async for user in reaction.users():
                        # excluding non-Members and self-reaction cases
                        if isinstance(user, Member) and user != guild.me:
                            members.add(user)

                    state_by_role[role] = members

        return revert_dict(state_by_role)

    @staticmethod
    def _rbr_message_content(guild: Guild, guild_data: GuildData) -> Embed:
        title = guild_data.title
        combinations = guild_data.combinations

        if combinations:
            content = ""
            for combination in combinations:
                emoji = combination.emoji
                try:
                    emoji = ImprovedList(guild.emojis).get_item(
                        emoji,
                        key=lambda e: e.id
                    )
                except ValueError:
                    pass

                role = guild.get_role(combination.role)
                if not role: # Role does not exist case
                    continue

                content += f"{emoji} - {role.name}" + "\n"

        else:
            content = "No combination registered yet"

        return Embed(
            title=title,
            description=content
        )

    @staticmethod
    async def import_emoji(ctx: Context, emoji: EmojiType) -> Union[str, Emoji]:
        emoji = str(emoji)

        try:
            emoji = await EmojiConverter().convert(ctx, emoji)
        except BadArgument:
            temp = demojize(emoji)
            if any([emoji == temp,               # Not an emoji
                    temp.count(":") != 2,        # More or less than an emoji
                    not temp.startswith(":"),    # More than an emoji
                    not temp.endswith(":")]):    # More than an emoji
                raise InvalidArguments(
                    ctx=ctx,
                    title="Emoji Error",
                    message=f"Couldn't load {emoji} emoji"
                )

        return emoji

    @staticmethod
    async def import_role(ctx: Context, role: RoleType) -> Role:
        try:
            return await RoleConverter().convert(ctx, str(role))
        except BadArgument:
            raise InvalidArguments(
                ctx=ctx,
                title="Role Error",
                message=f"Couldn't load {role} role"
            )
Example #2
0
class Event(Cog):

    ######################################### CONSTRUCTOR #########################################

    def __init__(self, bot):
        self.bot = bot

        self.config = Cfg(self)

        self.defaults_guild = GuildData(events=[])
        self.config.defaults_guild(self.defaults_guild)

        self.on = True
        self.tasks = []
        self.bot.loop.create_task(self.scheduler())

        print("EVENT_COG: loaded")

    ########################################### UNLOADER ##########################################

    def cog_unload(self):
        self.on = False
        for t in self.tasks:
            t.cancel()
            del t
        del self

        print("EVENT_COG: unloaded")

    ########################################## SCHEDULER ##########################################

    async def scheduler(self):
        guilds_configs = self.config.all_guilds()
        for guild_id, guild_config in guilds_configs.items():
            guild = self.bot.get_guild(guild_id)
            if guild:
                await self.add_events(guild, *(guild_config.get().events))

    ######################################## EVENT COMMANDS #######################################

    @admin_or_permissions(manage_roles=True)
    @group()
    async def event(self, ctx: Context):
        pass

    @admin_or_permissions(manage_roles=True)
    @event.command()
    async def add(self, ctx: Context, channel: Union[int, str, TextChannel],
                  time: str, title: str, *participants: Union[int, str, Member,
                                                              Role]):
        guild = ctx.guild

        try:
            # parsing title
            title = title[:200]

            # parsing date
            date = EventData.timestamp(time)

            # parsing participants (Member(s) or Role(s))
            async def convert(p: Union[int, str, Member, Role]):
                try:
                    return await RoleConverter().convert(ctx, str(p))
                except BadArgument:
                    try:
                        return await MemberConverter().convert(ctx, str(p))
                    except BadArgument:
                        raise InvalidArguments(
                            ctx=ctx,
                            title="Participant Error",
                            message="Couldn't find provided participants")

            participants = [await convert(p) for p in participants]

            # parsing channel
            try:
                channel = await TextChannelConverter().convert(
                    ctx, str(channel))
                channel_id = channel.id
            except BadArgument:
                try:  # keeping 0 value as a no-announcement-channel criterium
                    channel_id = int(channel)
                    if channel_id:
                        raise ValueError
                except ValueError:
                    raise InvalidArguments(
                        ctx=ctx,
                        title="Channel Error",
                        message="Couldn't find provided channel")

            guild_config = self.config.guild(guild)
            guild_data = guild_config.get()

            # checking if same event already exists
            events = ImprovedList(guild_data.events)
            try:
                events.index((title.lower(), date),
                             key=lambda e: (e.title.lower(), e.date))
                raise InvalidArguments(
                    ctx=ctx,
                    title="Name Error",
                    message="There is another event on same date with same name"
                )
            except ValueError:
                pass

            # appending event
            event = EventData(channel=channel_id,
                              date=date,
                              participants=[p.id for p in participants],
                              title=title)
            guild_data.events.append(event)
            guild_config.set(guild_data)

            # starting event scheduler
            await self.add_events(guild, event)

            embed = Embed(
                title="Event added",
                description=
                (f"Title: {title}" + "\n"
                 f"Date: {event.datetime()}" + "\n"
                 f"Participants: {' '.join(map(lambda p: p.mention, participants))}"
                 ))
            await ctx.send(embed=embed)

        except InvalidArguments as error:
            await error.execute()

    @admin_or_permissions(manage_roles=True)
    @event.command(name="list")
    async def list_(self, ctx: Context):
        guild = ctx.guild
        guild_config = self.config.guild(guild)
        guild_data = guild_config.get()

        events = [e for e in guild_data.events if not e.elapsed()]
        events = lexsorted(events, key=lambda e: (e.date, e.title))

        if events:

            def convert(p: int):
                ret = guild.get_member(p)
                if not ret:
                    ret = guild.get_role(p)
                return ret

            message = ""
            for event in events:
                participants = [
                    c for p in event.participants if (c := convert(p))
                ]
                message += (
                    f"{event.datetime()} - {event.title}: "
                    f"{', '.join(map(lambda p: p.mention, participants))}" +
                    "\n")
Example #3
0
File: cog.py Project: Ukabi/UkaBot
class Birthday(Cog):

    ######################################### CONSTRUCTOR #########################################

    def __init__(self, bot: Bot):
        self.bot = bot

        self.config = Cfg(self)

        self.defaults_guild = GuildData(channel=0, role=0)
        self.config.defaults_guild(self.defaults_guild)

        self.defaults_member = MemberData(birthday=Date(day=None, month=None),
                                          name="Unknown")
        self.config.defaults_member(self.defaults_member)

        self.on = True
        self.task = self.bot.loop.create_task(self.scheduler())
        print("BIRTHDAY_COG: loaded")

    ########################################### UNLOADER ##########################################

    def cog_unload(self):
        self.on = False
        self.task.cancel()
        del self.task
        del self

        print("BIRTHDAY_COG: unloaded")

    ########################################## SCHEDULER ##########################################

    async def scheduler(self):
        while self.on:
            await self.wait_for_tomorrow(self.bot.loop)
            if self.on:
                print("New day!")
                guilds_configs = self.config.all_guilds()
                for guild_id, guild_config in guilds_configs.items():
                    guild = self.bot.get_guild(guild_id)
                    if guild:
                        await self.treat_guild(guild, guild_config.get(),
                                               self.config.all_members(guild))

    ###################################### BIRTHDAY COMMANDS ######################################

    @group()
    async def birthday(self, ctx: Context):
        pass

    @admin()
    @birthday.command()
    async def check(self, ctx: Context):
        guild = ctx.guild
        await self.treat_guild(guild,
                               self.config.guild(guild).get(),
                               self.config.all_members(guild))

    @admin()
    @birthday.command()
    async def channel(self, ctx: Context, *, channel: Union[int, str,
                                                            TextChannel]):
        try:
            if not isinstance(channel, TextChannel):
                channel = await TextChannelConverter().convert(
                    ctx, str(channel))
        except BadArgument:
            error = InvalidArguments(
                ctx=ctx,
                title='Channel Error',
                message='Channel not found or not provided')
            await error.execute()
            return

        else:
            guild_config = self.config.guild(ctx.guild)
            guild_data = guild_config.get()
            guild_data.channel = channel.id
            guild_config.set(guild_data)

            embed = Embed(
                title='Channel Changed',
                description=f'Successfully updated channel to {channel.mention}'
            )
            await ctx.send(embed=embed)

    @admin()
    @birthday.command()
    async def role(self, ctx: Context, *, role: Union[int, str, Role]):
        try:
            if not isinstance(role, Role):
                role = await RoleConverter().convert(ctx, str(role))
        except BadArgument:
            error = InvalidArguments(ctx=ctx,
                                     title='Role Error',
                                     message='Role not found or not provided')
            await error.execute()

        else:
            guild_config = self.config.guild(ctx.guild)
            guild_data = guild_config.get()

            guild_data.role = role.id
            guild_config.set(guild_data)

            embed = Embed(
                title='Role Changed',
                description=f'Successfully updated role to {role.mention}')
            await ctx.send(embed=embed)

    async def _set_birthday(self, ctx: Context, member: Member, day: int,
                            month: int):
        try:
            date = Date.convert_date(day=day, month=month)
        except ValueError:
            error = InvalidArguments(
                ctx=ctx,
                title="Date Error",
                message="Couldn't understand provided date")
            await error.execute()

        else:
            member_config = self.config.member(member)

            member_data = MemberData(birthday=date, name=member.name)

            member_config.set(member_data)

            if ctx.author == member:
                desc = f"Birthday set to {date}"
            else:
                desc = f"Birthday of {member} has been set to {date}"

            embed = Embed(title="Birthday Set", description=desc)
            await ctx.send(embed=embed)

    @birthday.command(name='set')
    async def set_(self, ctx: Context, day: int, month: int):
        await self._set_birthday(ctx, ctx.author, day, month)

    @admin()
    @birthday.command()
    async def forceset(self, ctx: Context, member_id: int, day: int,
                       month: int):
        try:
            member = ctx.guild.get_member(int(member_id))
            if member:
                await self._set_birthday(ctx, member, day, month)
            else:
                raise InvalidArguments(ctx=ctx,
                                       title="Member Error",
                                       message="Couldn't find provided member")
        except InvalidArguments as error:
            await error.execute()
        except TypeError:
            error = InvalidArguments(
                ctx=ctx,
                title="Argument Error",
                message="Provided member id couldn't be parsed")

    @birthday.command()
    async def remove(self, ctx: Context):
        member = ctx.author
        member_config = self.config.member(member)

        new = self.defaults_member.copy()
        new.name = member.name

        member_config.set(new)

        embed = Embed(title="Birthday Reset",
                      description="Birthday has been removed from database")
        await ctx.send(embed=embed)

    @birthday.command(name='list')
    async def list_(self, ctx: Context):
        guild = ctx.guild
        members_configs = self.config.all_members(guild)

        # Dict[int, MemberData]
        members_data = {i: g.get() for i, g in members_configs.items()}

        # List[MemberData]
        to_sort = []
        for member_id, member_data in members_data.items():
            if not member_data.birthday:  # None case -> skip
                continue

            member = guild.get_member(member_id)
            if member:  # Member found case -> directly using their name
                member_data.name = member.name

            to_sort.append(member_data)

        if to_sort:
            # sorting lexicographically birthdays list for better readability
            def key(m: MemberData):
                return (m.birthday.month, m.birthday.day, m.name)

            # List[MemberData]
            sorted_birthdays = lexsorted(to_sort, key=key)

            message = "\n".join(map(str, sorted_birthdays))
            embed = Embed(title="Birthdays List", description=message)
            await ctx.send(embed=embed)

        else:
            embed = Embed(title="Birthdays List",
                          description="No birthday recorded yet")
            await ctx.send(embed=embed)

    ######################################## CLASS METHODS ########################################

    @classmethod
    async def treat_guild(cls, guild: Guild, guild_data: GuildData,
                          members_configs: Dict[int, Group]):
        # importing Members from their ids
        # Dict[Member, Group]
        members_configs = {
            guild.get_member(i): g
            for i, g in members_configs.items()
        }
        # Dict[Member, Group] with None cases filtered out
        members_configs = {m: g for m, g in members_configs.items() if m}

        # keeping trace of member names if they leave the server
        cls.update_names(members_configs)

        # matching current date with birthdays
        now = dt.now()
        today = Date(day=now.day, month=now.month)
        # Dict[Member, Date]
        members_birthdays = {
            m: g.get().birthday
            for m, g in members_configs.items()
        }
        # List[Member]
        to_treat = [m for m, b in members_birthdays.items() if today == b]

        # sending birthday message
        channel_id = guild_data.channel
        channel = guild.get_channel(channel_id)
        if channel:
            await cls.treat_members(channel, to_treat)

        # updating roles
        role_id = guild_data.role
        role = guild.get_role(role_id)
        if role and can_give_role(role, guild.me):
            await cls.treat_role(role, to_treat)

    ######################################## STATIC METHODS #######################################

    @staticmethod
    async def treat_members(channel: TextChannel, to_treat: List[Member]):
        for member in to_treat:
            await channel.send(
                f":tada: Happy birthday {member.mention}!!! :cake:")

    @staticmethod
    async def treat_role(role: Role, to_treat: List[Member]):
        for member in role.guild.members:
            roles = member.roles

            if (member in to_treat) and (role not in roles):
                await member.add_roles(role)

            elif (member not in to_treat) and (role in roles):
                await member.remove_roles(role)

    @staticmethod
    def update_names(members_configs: Dict[Member, Group]):
        for member, group in members_configs.items():
            member_data = group.get()
            member_data.name = member.name
            group.set(member_data)

    @staticmethod
    async def wait_for_tomorrow(loop: AbstractEventLoop):
        now = dt.now()
        date = dt.fromordinal(dt.today().toordinal())
        next_day = date + td(days=1)
        wait_time = (next_day - now).total_seconds()
        await sleep(wait_time, loop=loop)