Source code for tools.discord_manage_roles

"""Create, edit, delete Discord roles and manage role assignments."""

from __future__ import annotations

import logging
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from tool_context import ToolContext

logger = logging.getLogger(__name__)

TOOL_NAME = "discord_manage_roles"
TOOL_DESCRIPTION = (
    "Manage Discord roles. Actions: 'create', 'edit', 'delete', "
    "'add_to_member', 'remove_from_member'. Requires admin perms."
)
TOOL_PARAMETERS = {
    "type": "object",
    "properties": {
        "action": {
            "type": "string",
            "enum": [
                "create", "edit", "delete",
                "add_to_member", "remove_from_member",
            ],
            "description": "The role management action.",
        },
        "server_id": {
            "type": "string",
            "description": "Server (guild) ID.",
        },
        "role_id": {
            "type": "string",
            "description": "Role ID (for edit/delete/assign).",
        },
        "target_user_id": {
            "type": "string",
            "description": (
                "User ID to add/remove role from."
            ),
        },
        "name": {
            "type": "string",
            "description": "Role name (for create or edit).",
        },
        "color": {
            "type": "string",
            "description": "Color hex '#FF0000' or integer.",
        },
        "hoist": {
            "type": "boolean",
            "description": "Display role separately.",
        },
        "mentionable": {
            "type": "boolean",
            "description": "Allow mentioning the role.",
        },
        "permissions": {
            "type": "integer",
            "description": "Permissions bitfield.",
        },
        "position": {
            "type": "integer",
            "description": "Role hierarchy position.",
        },
        "reason": {
            "type": "string",
            "description": "Audit log reason.",
        },
    },
    "required": ["action", "server_id"],
}


[docs] async def run( action: str, server_id: str, role_id: str | None = None, target_user_id: str | None = None, name: str | None = None, color: str | None = None, hoist: bool | None = None, mentionable: bool | None = None, permissions: int | None = None, position: int | None = None, reason: str | None = None, ctx: ToolContext | None = None, ) -> str: """Execute this tool and return the result. Args: action (str): The action value. server_id (str): The server id value. role_id (str | None): The role id value. target_user_id (str | None): The target user id value. name (str | None): Human-readable name. color (str | None): The color value. hoist (bool | None): The hoist value. mentionable (bool | None): The mentionable value. permissions (int | None): The permissions value. position (int | None): The position value. reason (str | None): The reason value. ctx (ToolContext | None): Tool execution context providing access to bot internals. Returns: str: Result string. """ import discord from tools._discord_helpers import ( require_discord_client, check_admin_permission, ) client = require_discord_client(ctx) if isinstance(client, str): return client user_id = ctx.user_id if ctx else "" ok, err = await check_admin_permission( client, user_id, server_id, ) if not ok: return err try: server = client.get_guild(int(server_id)) except ValueError: return f"Error: Invalid server ID: '{server_id}'." if not server: return f"Error: Server '{server_id}' not found." def _parse_colour(): """Internal helper: parse colour. """ if color is None: return None try: s = color.strip() if s.startswith("#"): return discord.Colour(int(s[1:], 16)) return discord.Colour(int(s)) except (ValueError, AttributeError): return None # --- CREATE --- if action == "create": if not name: return "Error: 'name' is required for create." kwargs: dict = { "name": name, "hoist": hoist or False, "mentionable": mentionable or False, } colour = _parse_colour() if colour is not None: kwargs["colour"] = colour if permissions is not None: kwargs["permissions"] = discord.Permissions(permissions) try: role = await server.create_role(**kwargs) if position is not None: await role.edit(position=position) return ( f"Created role '{role.name}' (ID: {role.id}) " f"in '{server.name}'." ) except discord.errors.Forbidden: return "Error: No permission to create roles." except Exception as exc: return f"Error creating role: {exc}" # --- EDIT --- if action == "edit": if not role_id: return "Error: 'role_id' is required for edit." role = server.get_role(int(role_id)) if not role: return f"Error: Role '{role_id}' not found." if role.is_default(): return "Error: Cannot edit the @everyone role." kwargs = {} if name is not None: kwargs["name"] = name colour = _parse_colour() if colour is not None: kwargs["colour"] = colour if hoist is not None: kwargs["hoist"] = hoist if mentionable is not None: kwargs["mentionable"] = mentionable if permissions is not None: kwargs["permissions"] = discord.Permissions(permissions) if not kwargs and position is None: return "Error: No parameters provided to edit." try: if kwargs: await role.edit(**kwargs) if position is not None: await role.edit(position=position) return f"Successfully edited role '{role.name}'." except discord.errors.Forbidden: return "Error: No permission to edit roles." except Exception as exc: return f"Error editing role: {exc}" # --- DELETE --- if action == "delete": if not role_id: return "Error: 'role_id' is required for delete." role = server.get_role(int(role_id)) if not role: return f"Error: Role '{role_id}' not found." if role.is_default(): return "Error: Cannot delete the @everyone role." rname = role.name try: await role.delete() return f"Deleted role '{rname}' from '{server.name}'." except discord.errors.Forbidden: return "Error: No permission to delete roles." except Exception as exc: return f"Error deleting role: {exc}" # --- ADD_TO_MEMBER --- if action == "add_to_member": if not role_id or not target_user_id: return ( "Error: 'role_id' and 'target_user_id' are " "required for add_to_member." ) role = server.get_role(int(role_id)) if not role: return f"Error: Role '{role_id}' not found." member = server.get_member(int(target_user_id)) if not member: return ( f"Error: User '{target_user_id}' not found " f"in '{server.name}'." ) if role in member.roles: return ( f"User '{member.name}' already has role " f"'{role.name}'." ) try: await member.add_roles( role, reason=reason or "Assigned via bot", ) return ( f"Added role '{role.name}' to '{member.name}'." ) except discord.errors.Forbidden: return "Error: No permission to manage roles." except Exception as exc: return f"Error adding role: {exc}" # --- REMOVE_FROM_MEMBER --- if action == "remove_from_member": if not role_id or not target_user_id: return ( "Error: 'role_id' and 'target_user_id' are " "required for remove_from_member." ) role = server.get_role(int(role_id)) if not role: return f"Error: Role '{role_id}' not found." member = server.get_member(int(target_user_id)) if not member: return ( f"Error: User '{target_user_id}' not found " f"in '{server.name}'." ) if role not in member.roles: return ( f"User '{member.name}' does not have role " f"'{role.name}'." ) try: await member.remove_roles( role, reason=reason or "Removed via bot", ) return ( f"Removed role '{role.name}' from " f"'{member.name}'." ) except discord.errors.Forbidden: return "Error: No permission to manage roles." except Exception as exc: return f"Error removing role: {exc}" return f"Error: Unknown action '{action}'."