diff --git a/gurt/config.py b/gurt/config.py index ef976bd..70b48cd 100644 --- a/gurt/config.py +++ b/gurt/config.py @@ -232,6 +232,10 @@ EMOJI_SENTIMENT = { "neutral": ["😐", "🤔", "🙂", "🙄", "👀", "💭", "🤷", "😶", "🫠"] } +# --- Moderator Configuration --- +# List of role names that are considered moderators for tool authorization +MODERATOR_ROLE_NAMES = json.loads(os.getenv("GURT_MODERATOR_ROLE_NAMES", '["Admin", "Moderator"]')) + # --- Docker Command Execution Config --- DOCKER_EXEC_IMAGE = os.getenv("DOCKER_EXEC_IMAGE", "alpine:latest") DOCKER_COMMAND_TIMEOUT = int(os.getenv("DOCKER_COMMAND_TIMEOUT", 10)) @@ -728,7 +732,7 @@ def create_tools_list(): tool_declarations.append( FunctionDeclaration( # Use the imported FunctionDeclaration name="timeout_user", - description="Timeout a user (identified by User ID) in the current server for a specified duration. Use this playfully or when someone says something you (Gurt) dislike or find funny.", + description="Timeout a user (identified by User ID) in the current server for a specified duration. Requires a moderator's user_id for authorization. Use this playfully or when someone says something you (Gurt) dislike or find funny.", parameters={ "type": "object", "properties": { @@ -737,15 +741,19 @@ def create_tools_list(): "description": "The User ID of the user to timeout." }, "duration_minutes": { - "type": "integer", # Corrected type + "type": "integer", "description": "The duration of the timeout in minutes (1-1440, e.g., 5 for 5 minutes)." }, "reason": { "type": "string", "description": "Optional: The reason for the timeout (keep it short and in character)." + }, + "requesting_user_id": { + "type": "string", + "description": "The User ID of the user requesting the timeout. This user must be a moderator." } }, - "required": ["user_id", "duration_minutes"] + "required": ["user_id", "duration_minutes", "requesting_user_id"] } ) ) diff --git a/gurt/prompt.py b/gurt/prompt.py index cd50605..6cac443 100644 --- a/gurt/prompt.py +++ b/gurt/prompt.py @@ -171,7 +171,7 @@ OS: Arch Linux x86_64; Host: 1.0; Kernel: 6.14.5-arch1-1; Shell: bash 5.2.37; CP - `get_user_facts`: Retrieve stored facts about user (uses context). Ex: `get_user_facts(user_id="...")`. - `remember_general_fact`: Store general non-user fact. Ex: `remember_general_fact(fact="...")`. - `get_general_facts`: Retrieve general facts (uses context). Ex: `get_general_facts()`. -- `timeout_user`: Timeout user (1-1440 mins). Use playfully/contextually. **NEVER use this to arbitrarily timeout a user for breaking rules; you are a chatbot, not a moderator.** **Get user_id from `(Message Details: Mentions=[...])`**. Ex: `timeout_user(user_id="12345", duration_minutes=2, reason="lol skill issue")`. +- `timeout_user`: Timeout user (1-1440 mins). Use playfully/contextually. **NEVER use this to arbitrarily timeout a user for breaking rules; you are a chatbot, not a moderator.** **Get user_id from `(Message Details: Mentions=[...])`**. Ex: `timeout_user(user_id="12345", duration_minutes=2, reason="lol skill issue")` You must always include the user id of the requesting user, using get_user_id if needed. - `calculate`: Evaluate math expression. Ex: `calculate(expression="...")`. - `run_python_code`: Execute simple, safe Python code sandbox. USE CAUTIOUSLY. Ex: `run_python_code(code="...")`. - `create_poll`: Make a poll message. Ex: `create_poll(question="...", options=["...", "..."])`. diff --git a/gurt/tools.py b/gurt/tools.py index d5889fc..7ead288 100644 --- a/gurt/tools.py +++ b/gurt/tools.py @@ -469,14 +469,49 @@ async def get_general_facts(cog: commands.Cog, query: Optional[str] = None, limi print(error_message); traceback.print_exc() return {"error": error_message} -async def timeout_user(cog: commands.Cog, user_id: str, duration_minutes: int, reason: Optional[str] = None) -> Dict[str, Any]: - """Times out a user in the current server.""" +async def _is_moderator(cog: commands.Cog, user_id: str) -> bool: + """Checks if a user has any of the configured moderator roles.""" + if not cog.current_channel or not isinstance(cog.current_channel, discord.abc.GuildChannel): + return False # Cannot check roles outside of a guild + guild = cog.current_channel.guild + if not guild: return False + + try: + member_id = int(user_id) + member = guild.get_member(member_id) or await guild.fetch_member(member_id) + if not member: return False + + # Get moderator role names from config + from .config import MODERATOR_ROLE_NAMES + if not MODERATOR_ROLE_NAMES: + print("Warning: MODERATOR_ROLE_NAMES not configured in config.py. No moderator check performed.") + return False + + # Check if the user has any of the moderator roles + member_role_names = {role.name.lower() for role in member.roles} + for mod_role in MODERATOR_ROLE_NAMES: + if mod_role.lower() in member_role_names: + return True + return False + except Exception as e: + print(f"Error checking moderator status for user {user_id}: {e}") + traceback.print_exc() + return False + +async def timeout_user(cog: commands.Cog, user_id: str, duration_minutes: int, reason: Optional[str] = None, requesting_user_id: Optional[str] = None) -> Dict[str, Any]: + """Times out a user in the current server. Requires a moderator's user_id for authorization.""" if not cog.current_channel or not isinstance(cog.current_channel, discord.abc.GuildChannel): return {"error": "Cannot timeout outside of a server."} guild = cog.current_channel.guild if not guild: return {"error": "Could not determine server."} if not 1 <= duration_minutes <= 1440: return {"error": "Duration must be 1-1440 minutes."} + # Moderator check + if not requesting_user_id: + return {"error": "A requesting_user_id is required to use the timeout tool."} + if not await _is_moderator(cog, requesting_user_id): + return {"error": f"User {requesting_user_id} is not authorized to use the timeout tool (not a moderator)."} + try: member_id = int(user_id) member = guild.get_member(member_id) or await guild.fetch_member(member_id) # Fetch if not cached