feat: Add moderator role configuration and authorization check for timeout user functionality

This commit is contained in:
Slipstream 2025-05-30 23:06:20 -06:00
parent 99bd17008e
commit 63b82bad2c
Signed by: slipstream
GPG Key ID: 13E498CE010AC6FD
3 changed files with 49 additions and 6 deletions

View File

@ -232,6 +232,10 @@ EMOJI_SENTIMENT = {
"neutral": ["😐", "🤔", "🙂", "🙄", "👀", "💭", "🤷", "😶", "🫠"] "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 Command Execution Config ---
DOCKER_EXEC_IMAGE = os.getenv("DOCKER_EXEC_IMAGE", "alpine:latest") DOCKER_EXEC_IMAGE = os.getenv("DOCKER_EXEC_IMAGE", "alpine:latest")
DOCKER_COMMAND_TIMEOUT = int(os.getenv("DOCKER_COMMAND_TIMEOUT", 10)) DOCKER_COMMAND_TIMEOUT = int(os.getenv("DOCKER_COMMAND_TIMEOUT", 10))
@ -728,7 +732,7 @@ def create_tools_list():
tool_declarations.append( tool_declarations.append(
FunctionDeclaration( # Use the imported FunctionDeclaration FunctionDeclaration( # Use the imported FunctionDeclaration
name="timeout_user", 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={ parameters={
"type": "object", "type": "object",
"properties": { "properties": {
@ -737,15 +741,19 @@ def create_tools_list():
"description": "The User ID of the user to timeout." "description": "The User ID of the user to timeout."
}, },
"duration_minutes": { "duration_minutes": {
"type": "integer", # Corrected type "type": "integer",
"description": "The duration of the timeout in minutes (1-1440, e.g., 5 for 5 minutes)." "description": "The duration of the timeout in minutes (1-1440, e.g., 5 for 5 minutes)."
}, },
"reason": { "reason": {
"type": "string", "type": "string",
"description": "Optional: The reason for the timeout (keep it short and in character)." "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"]
} }
) )
) )

View File

@ -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="...")`. - `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="...")`. - `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()`. - `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="...")`. - `calculate`: Evaluate math expression. Ex: `calculate(expression="...")`.
- `run_python_code`: Execute simple, safe Python code sandbox. USE CAUTIOUSLY. Ex: `run_python_code(code="...")`. - `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=["...", "..."])`. - `create_poll`: Make a poll message. Ex: `create_poll(question="...", options=["...", "..."])`.

View File

@ -469,14 +469,49 @@ async def get_general_facts(cog: commands.Cog, query: Optional[str] = None, limi
print(error_message); traceback.print_exc() print(error_message); traceback.print_exc()
return {"error": error_message} return {"error": error_message}
async def timeout_user(cog: commands.Cog, user_id: str, duration_minutes: int, reason: Optional[str] = None) -> Dict[str, Any]: async def _is_moderator(cog: commands.Cog, user_id: str) -> bool:
"""Times out a user in the current server.""" """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): if not cog.current_channel or not isinstance(cog.current_channel, discord.abc.GuildChannel):
return {"error": "Cannot timeout outside of a server."} return {"error": "Cannot timeout outside of a server."}
guild = cog.current_channel.guild guild = cog.current_channel.guild
if not guild: return {"error": "Could not determine server."} if not guild: return {"error": "Could not determine server."}
if not 1 <= duration_minutes <= 1440: return {"error": "Duration must be 1-1440 minutes."} 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: try:
member_id = int(user_id) member_id = int(user_id)
member = guild.get_member(member_id) or await guild.fetch_member(member_id) # Fetch if not cached member = guild.get_member(member_id) or await guild.fetch_member(member_id) # Fetch if not cached