From 193b5ad440e6e18d1dc2e37e6c07fa7447fcf15f Mon Sep 17 00:00:00 2001 From: perforat Date: Tue, 14 Apr 2026 15:25:26 +0000 Subject: [PATCH] add role_manager cog --- cogs/role_manager/config.json | 18 ++++ cogs/role_manager/role_manager.py | 161 ++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 cogs/role_manager/config.json create mode 100644 cogs/role_manager/role_manager.py diff --git a/cogs/role_manager/config.json b/cogs/role_manager/config.json new file mode 100644 index 0000000..4816043 --- /dev/null +++ b/cogs/role_manager/config.json @@ -0,0 +1,18 @@ +{ + "description": "(Usage is Admin Only) Role system manager", + "usages": [ + "setup_role_menu", + "add_role <@role>", + "remove_role ", + "edit_menu_text " + ], + "hidden": false, + "CHANNEL_ID": 1489648316211007629, + "MESSAGE_ID": 1493631125732720650, + "MENU_TITLE": "Server Roles", + "ROLE_MESSAGE_TEXT": "Click the reactions below to claim your roles", + "REACTION_ROLES": { + "🎮": 9876543210, + "🎨": 1122334455 + } +} \ No newline at end of file diff --git a/cogs/role_manager/role_manager.py b/cogs/role_manager/role_manager.py new file mode 100644 index 0000000..ddc8531 --- /dev/null +++ b/cogs/role_manager/role_manager.py @@ -0,0 +1,161 @@ +import discord +from discord.ext import commands +import logging +import asyncio +import json +import os + +logger = logging.getLogger(__name__) + +class RoleManager(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.config_path = os.path.join(os.path.dirname(__file__), 'config.json') + self.config = self.load_config() + + def load_config(self): + with open(self.config_path, 'r', encoding='utf-8') as f: + return json.load(f) + + def save_config(self): + with open(self.config_path, 'w', encoding='utf-8') as f: + json.dump(self.config, f, indent=4, ensure_ascii=False) + + async def cog_load(self): + asyncio.create_task(self._startup()) + + async def _startup(self): + await self.bot.wait_until_ready() + if self.config.get("MESSAGE_ID"): + await self.check_and_sync_roles() + + async def handle_reaction(self, payload, add_role=True): + if payload.message_id != self.config.get("MESSAGE_ID"): + return + + emoji = str(payload.emoji) + role_id = self.config.get("REACTION_ROLES", {}).get(emoji) + if not role_id: + return + + guild = self.bot.get_guild(payload.guild_id) + member = guild.get_member(payload.user_id) + if not member or member.bot: + return + + role = guild.get_role(role_id) + if not role: + return + + try: + if add_role: + await member.add_roles(role) + else: + await member.remove_roles(role) + except discord.Forbidden: + logger.error(f"Missing permissions for role {role.name}") + + @commands.Cog.listener() + async def on_raw_reaction_add(self, payload): + await self.handle_reaction(payload, add_role=True) + + @commands.Cog.listener() + async def on_raw_reaction_remove(self, payload): + await self.handle_reaction(payload, add_role=False) + + @commands.command(name="setup_role_menu") + @commands.has_permissions(administrator=True) + async def setup_role_menu(self, ctx): + chan_id = self.config.get("CHANNEL_ID") + channel = self.bot.get_channel(chan_id) or ctx.channel + + content = self.config.get("ROLE_MESSAGE_TEXT", "React for roles!") + message = await channel.send(content) + + self.config["MESSAGE_ID"] = message.id + self.config["CHANNEL_ID"] = channel.id + self.save_config() + + for emoji in self.config.get("REACTION_ROLES", {}): + await message.add_reaction(emoji) + + @commands.command(name="add_role") + @commands.has_permissions(administrator=True) + async def add_role(self, ctx, emoji: str, role: discord.Role): + if "REACTION_ROLES" not in self.config: + self.config["REACTION_ROLES"] = {} + + self.config["REACTION_ROLES"][emoji] = role.id + self.save_config() + if self.config.get("MESSAGE_ID"): + try: + chan = self.bot.get_channel(self.config["CHANNEL_ID"]) + msg = await chan.fetch_message(self.config["MESSAGE_ID"]) + await msg.add_reaction(emoji) + except Exception as e: + logger.error("failed to add reaction to menu: %s", e) + + @commands.command(name="remove_role") + @commands.has_permissions(administrator=True) + async def remove_role(self, ctx, emoji: str): + if emoji in self.config.get("REACTION_ROLES", {}): + del self.config["REACTION_ROLES"][emoji] + self.save_config() + + @commands.command(name="edit_menu_text") + @commands.has_permissions(administrator=True) + async def edit_menu_text(self, ctx, *, new_text: str): + self.config["ROLE_MESSAGE_TEXT"] = new_text + self.save_config() + if self.config.get("MESSAGE_ID"): + try: + chan = self.bot.get_channel(self.config["CHANNEL_ID"]) + msg = await chan.fetch_message(self.config["MESSAGE_ID"]) + await msg.edit(content=new_text) + logger.info("menu text updated") + except discord.NotFound: + logger.error("could not find message when updating role message") + except discord.Forbidden: + logger.error("no permission err when updating role message") + except discord.HTTPException as e: + logger.error("http exception when updating role message: %s", e) + else: + logger.warning("text saved but no message id found, cannot update role menu text") + + async def check_and_sync_roles(self): + try: + chan = await self.bot.fetch_channel(self.config["CHANNEL_ID"]) + msg = await chan.fetch_message(self.config["MESSAGE_ID"]) + + reacted_users = {} + for reaction in msg.reactions: + emoji = str(reaction.emoji) + if emoji in self.config.get("REACTION_ROLES", {}): + reacted_users[emoji] = set() + async for user in reaction.users(): + if not user.bot: + reacted_users[emoji].add(user.id) + + for emoji, role_id in self.config.get("REACTION_ROLES", {}).items(): + role = msg.guild.get_role(role_id) + if not role: + continue + + reactors = reacted_users.get(emoji, set()) + + for member in msg.guild.members: + if member.bot: + continue + has_role = role in member.roles + has_reacted = member.id in reactors + + if has_reacted and not has_role: + await member.add_roles(role) + elif not has_reacted and has_role: + await member.remove_roles(role) + + except Exception as e: + logger.error("check_and_sync_roles failed: %s", e) + +async def setup(bot): + await bot.add_cog(RoleManager(bot)) \ No newline at end of file