diff --git a/robocop_ng/cogs/ryujinx_reactionroles.py b/robocop_ng/cogs/ryujinx_reactionroles.py index cfe2b99..351ced1 100644 --- a/robocop_ng/cogs/ryujinx_reactionroles.py +++ b/robocop_ng/cogs/ryujinx_reactionroles.py @@ -1,10 +1,12 @@ +import collections import json import config import os import discord +from discord.ext import commands from discord.ext.commands import Cog - +from helpers.checks import check_if_staff class RyujinxReactionRoles(Cog): def __init__(self, bot): @@ -13,46 +15,86 @@ class RyujinxReactionRoles(Cog): config.reaction_roles_channel_id ) # The channel to send the reaction role message. (self-roles channel) - self.emoji_map = { - "🦑": "Looking for LDN game (Splatoon 2)", - "👹": "Looking for LDN game (Monster Hunter Generations Ultimate)", - "👺": "Looking for LDN game (Monster Hunter Rise)", - "🧩": "Looking for LDN game (Mario Party Superstars)", - "🐉": "Looking for LDN game (Pokémon Sword/Shield)", - "⚔️": "Looking for LDN game (Super Smash Bros. Ultimate)", - "🏎️": "Looking for LDN game (Mario Kart 8)", - "🪨": "Looking for LDN game (Pokémon Brilliant Diamond/Shining Pearl)", - "🍃": "Looking for LDN game (Animal Crossing: New Horizons)", - "➡": "Looking for LDN game (Others)", - "🚩": "Testers", - # LDN roles should be placed *before* testers role, because of embed generating. - # LDN roles ought to be in the format "Looking for LDN game ()". - } # The mapping of emoji ids to the role. - self.file = "data/reactionroles.json" # the file to store the required reaction role data. (message id of the RR message.) self.msg_id = None self.m = None # the msg object - self.get_role = lambda emoji_name: discord.utils.get( + @commands.guild_only() + @commands.check(check_if_staff) + @commands.command() + async def register_reaction_role(self, ctx, target_role_id: int, emoji_name: str): + """Register a reaction role, staff only.""" + + if emoji_name[0] == '<': + emoji_name = emoji_name[1:-1] + + if target_role_id in config.staff_role_ids: + return await ctx.send("Error: Dangerous role found!") + + target_role = ctx.guild.get_role(target_role_id) + + if target_role is None: + return await ctx.send("Error: Role not found!") + + target_role_name = target_role.name + + for key in self.reaction_config["reaction_roles_emoji_map"]: + value = self.reaction_config["reaction_roles_emoji_map"][key] + if type(value) is str and target_role_name == value: + return await ctx.send(f"Error: {target_role_name}: already registered.") + + self.reaction_config["reaction_roles_emoji_map"][emoji_name] = target_role_name + self.save_reaction_config(self.reaction_config) + await self.reload_reaction_message(False) + + await ctx.send(f"{target_role_name}: registered.") + + def get_emoji_full_name(self, emoji): + emoji_name = emoji.name + if emoji_name is not None and emoji.id is not None: + emoji_name = f":{emoji_name}:{emoji.id}" + + return emoji_name + + def get_role(self, key): + return discord.utils.get( self.bot.guilds[0].roles, - name=self.emoji_map.get(str(emoji_name)), + name=self.get_role_from_emoji(key), ) - async def generate_embed(self): - emojis = list(self.emoji_map.keys()) - description = "React to this message with the emojis given below to get your 'Looking for LDN game' roles. \n\n" + def get_role_from_emoji(self, key): + value = self.emoji_map.get(key) - for x in emojis: - if self.emoji_map.get(x) == "Testers": - description += f'\nReact {x} to get the "{self.emoji_map.get(x)}" role.' + if value is not None and type(value) is not str: + return value.get("role") + + return value + + async def generate_embed(self): + last_descrption = [] + description = ["React to this message with the emojis given below to get your 'Looking for LDN game' roles. \n"] + + for x in self.emoji_map: + value = self.emoji_map[x] + + emoji = x + if len(emoji.split(':')) == 3: + emoji = f"<{emoji}>" + + if type(value) is str: + + description.append(f"{emoji} for __{self.emoji_map.get(x).split('(')[1].split(')')[0]}__") else: - description += ( - f"{x} for __{self.emoji_map.get(x).split('(')[1].split(')')[0]}__ \n" - ) + role_name = value["role"] + line_fmt = value["fmt"] + if value.get("should_be_last", False): + last_descrption.append(line_fmt.format(emoji, role_name)) + else: + description.append(line_fmt.format(emoji, role_name)) embed = discord.Embed( - title="**Select your roles**", description=description, color=420420 + title="**Select your roles**", description='\n'.join(description) + '\n' + '\n'.join(last_descrption), color=420420 ) embed.set_footer( text="To remove a role, simply remove the corresponding reaction." @@ -63,8 +105,12 @@ class RyujinxReactionRoles(Cog): async def handle_offline_reaction_add(self): for reaction in self.m.reactions: for user in await reaction.users().flatten(): - if self.emoji_map.get(reaction.emoji) is not None: - role = self.get_role(reaction.emoji) + emoji_name = str(reaction.emoji) + if emoji_name[0] == '<': + emoji_name = emoji_name[1:-1] + + if self.get_role_from_emoji(emoji_name) is not None: + role = self.get_role(emoji_name) if not user in role.members and not user.bot: await user.add_roles(role) else: @@ -73,27 +119,38 @@ class RyujinxReactionRoles(Cog): async def handle_offline_reaction_remove(self): for emoji in self.emoji_map: for reaction in self.m.reactions: - role = self.get_role(reaction.emoji) + emoji_name = str(reaction.emoji) + if emoji_name[0] == '<': + emoji_name = emoji_name[1:-1] + + role = self.get_role(emoji_name) for user in role.members: if user not in await reaction.users().flatten(): await self.m.guild.get_member(user.id).remove_roles(role) - @Cog.listener() - async def on_ready(self): + def load_reaction_config(self): + if not os.path.exists(self.file): + self.bot.log.error("HERE?!") + with open(self.file, "w") as f: + json.dump({}, f) + + with open(self.file, "r") as f: + return json.load(f) + + def save_reaction_config(self, value): + with open(self.file, "w") as f: + json.dump(value, f) + + async def reload_reaction_message(self, should_handle_offline = True): + self.emoji_map = collections.OrderedDict(sorted(self.reaction_config["reaction_roles_emoji_map"].items(), key=lambda x: str(x[1]))) + guild = self.bot.guilds[0] # The ryu guild in which the bot is. channel = guild.get_channel(self.channel_id) - if not os.path.exists(self.file): - with open(self.file, "w") as f: - f.write("{}") - - with open(self.file, "r") as f: - id = json.load(f).get("id") - - m = discord.utils.get(await channel.history().flatten(), id=id) + m = discord.utils.get(await channel.history().flatten(), id=self.reaction_config["id"]) if m is None: - os.remove(self.file) + self.reaction_config["id"] = None embed = await self.generate_embed() self.m = await channel.send(embed=embed) @@ -102,13 +159,13 @@ class RyujinxReactionRoles(Cog): for x in self.emoji_map: await self.m.add_reaction(x) - with open(self.file, "w") as f: - json.dump({"id": self.m.id}, f) + self.reaction_config["id"] = self.m.id + self.save_reaction_config(self.reaction_config) await self.handle_offline_reaction_remove() else: - self.m = discord.utils.get(await channel.history().flatten(), id=id) + self.m = discord.utils.get(await channel.history().flatten(), id=self.reaction_config["id"]) self.msg_id = self.m.id await self.m.edit(embed=await self.generate_embed()) @@ -116,8 +173,15 @@ class RyujinxReactionRoles(Cog): if not x in self.m.reactions: await self.m.add_reaction(x) - await self.handle_offline_reaction_add() - await self.handle_offline_reaction_remove() + if should_handle_offline: + await self.handle_offline_reaction_add() + await self.handle_offline_reaction_remove() + + @Cog.listener() + async def on_ready(self): + self.reaction_config = self.load_reaction_config() + + await self.reload_reaction_message() @Cog.listener() async def on_raw_reaction_add(self, payload): @@ -125,28 +189,38 @@ class RyujinxReactionRoles(Cog): pass else: if payload.message_id == self.msg_id: - if self.emoji_map.get(payload.emoji.name) is not None: - if self.get_role(payload.emoji.name) is not None: + emoji_name = self.get_emoji_full_name(payload.emoji) + + if self.get_role_from_emoji(emoji_name) is not None: + target_role = self.get_role(emoji_name) + + if target_role is not None: await payload.member.add_roles( - self.get_role(payload.emoji.name) + target_role ) else: - print(f"Role {self.emoji_map[payload.emoji.name]} not found.") + self.bot.log.error(f"Role {self.emoji_map[emoji_name]} not found.") + await self.m.clear_reaction(payload.emoji) else: - await self.m.clear_reaction(payload.emoji.name) + await self.m.clear_reaction(payload.emoji) @Cog.listener() async def on_raw_reaction_remove(self, payload): if payload.message_id == self.msg_id: - if self.emoji_map.get(str(payload.emoji.name)) is not None: + emoji_name = self.get_emoji_full_name(payload.emoji) + + if self.get_role_from_emoji(emoji_name) is not None: guild = discord.utils.find( lambda guild: guild.id == payload.guild_id, self.bot.guilds ) - await guild.get_member(payload.user_id).remove_roles( - self.get_role(payload.emoji.name) - ) # payload.member.remove_roles will throw error + target_role = self.get_role(emoji_name) + + if target_role is not None: + await guild.get_member(payload.user_id).remove_roles( + self.get_role(emoji_name) + ) # payload.member.remove_roles will throw error async def setup(bot):