"""Create, edit, and delete Discord channels."""
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_channels"
TOOL_DESCRIPTION = (
"Manage Discord channels. Actions: 'create_text', "
"'create_voice', 'create_category', 'edit', 'delete'. "
"Requires admin permissions."
)
TOOL_PARAMETERS = {
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": [
"create_text",
"create_voice",
"create_category",
"edit",
"delete",
],
"description": "The channel management action.",
},
"server_id": {
"type": "string",
"description": (
"Server ID (required for create actions)."
),
},
"channel_id": {
"type": "string",
"description": (
"Channel ID (required for edit/delete)."
),
},
"name": {
"type": "string",
"description": "Channel name (for create or edit).",
},
"category_id": {
"type": "string",
"description": "Category ID to place the channel in.",
},
"topic": {
"type": "string",
"description": "Channel topic (text channels).",
},
"nsfw": {
"type": "boolean",
"description": "Mark as NSFW (text channels).",
},
"slowmode_delay": {
"type": "integer",
"description": "Slowmode in seconds (0-21600).",
},
"bitrate": {
"type": "integer",
"description": "Bitrate for voice channels.",
},
"user_limit": {
"type": "integer",
"description": "User limit for voice channels (0-99).",
},
"position": {
"type": "integer",
"description": "Position in the channel list.",
},
},
"required": ["action"],
}
[docs]
async def run(
action: str,
server_id: str | None = None,
channel_id: str | None = None,
name: str | None = None,
category_id: str | None = None,
topic: str | None = None,
nsfw: bool | None = None,
slowmode_delay: int | None = None,
bitrate: int | None = None,
user_limit: int | None = None,
position: int | None = None,
ctx: ToolContext | None = None,
) -> str:
"""Execute this tool and return the result.
Args:
action (str): The action value.
server_id (str | None): The server id value.
channel_id (str | None): Discord/Matrix channel identifier.
name (str | None): Human-readable name.
category_id (str | None): The category id value.
topic (str | None): The topic value.
nsfw (bool | None): The nsfw value.
slowmode_delay (int | None): The slowmode delay value.
bitrate (int | None): The bitrate value.
user_limit (int | None): The user limit value.
position (int | None): The position 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 ""
# --- CREATE actions ---
if action in ("create_text", "create_voice", "create_category"):
if not server_id:
return "Error: server_id is required for create actions."
if not name:
return "Error: name is required for create actions."
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."
cat = None
if category_id:
cat = client.get_channel(int(category_id))
if not isinstance(cat, discord.CategoryChannel):
return (
f"Error: '{category_id}' is not a category."
)
kwargs: dict = {"name": name}
if cat is not None:
kwargs["category"] = cat
if position is not None:
kwargs["position"] = position
try:
if action == "create_text":
if topic is not None:
kwargs["topic"] = topic
if nsfw is not None:
kwargs["nsfw"] = nsfw
if slowmode_delay is not None:
kwargs["slowmode_delay"] = slowmode_delay
ch = await server.create_text_channel(**kwargs)
elif action == "create_voice":
if bitrate is not None:
kwargs["bitrate"] = bitrate
if user_limit is not None:
kwargs["user_limit"] = user_limit
ch = await server.create_voice_channel(**kwargs)
else: # create_category
ch = await server.create_category_channel(**kwargs)
return (
f"Successfully created '{ch.name}' "
f"(ID: {ch.id}) in '{server.name}'."
)
except discord.errors.Forbidden:
return (
"Error: I don't have permission to create "
"channels in this server."
)
except discord.errors.HTTPException as exc:
return f"Error creating channel: {exc}"
except Exception as exc:
return f"An unexpected error occurred: {exc}"
# --- EDIT action ---
if action == "edit":
if not channel_id:
return "Error: channel_id is required for edit."
try:
ch = client.get_channel(int(channel_id))
except ValueError:
return f"Error: Invalid channel ID: '{channel_id}'."
if not ch or not hasattr(ch, "edit"):
return f"Error: Channel '{channel_id}' not found."
guild = getattr(ch, "guild", None)
if not guild:
return "Error: Channel is not in a guild."
ok, err = await check_admin_permission(
client, user_id, str(guild.id),
)
if not ok:
return err
kwargs = {}
if name is not None:
kwargs["name"] = name
if position is not None:
kwargs["position"] = position
if isinstance(ch, discord.TextChannel):
if topic is not None:
kwargs["topic"] = topic
if nsfw is not None:
kwargs["nsfw"] = nsfw
if slowmode_delay is not None:
kwargs["slowmode_delay"] = slowmode_delay
elif isinstance(ch, discord.VoiceChannel):
if user_limit is not None:
kwargs["user_limit"] = user_limit
if bitrate is not None:
kwargs["bitrate"] = bitrate
if not kwargs:
return "Error: No valid parameters provided to edit."
try:
await ch.edit(**kwargs)
return (
f"Successfully edited channel "
f"'{getattr(ch, 'name', channel_id)}'."
)
except discord.errors.Forbidden:
return "Error: I don't have permission to edit."
except Exception as exc:
return f"Error editing channel: {exc}"
# --- DELETE action ---
if action == "delete":
if not channel_id:
return "Error: channel_id is required for delete."
try:
ch = client.get_channel(int(channel_id))
except ValueError:
return f"Error: Invalid channel ID: '{channel_id}'."
if not ch or not hasattr(ch, "delete"):
return f"Error: Channel '{channel_id}' not found."
guild = getattr(ch, "guild", None)
if not guild:
return "Error: Channel is not in a guild."
ok, err = await check_admin_permission(
client, user_id, str(guild.id),
)
if not ok:
return err
ch_name = getattr(ch, "name", "Unknown")
try:
await ch.delete()
return (
f"Successfully deleted channel '{ch_name}' "
f"from '{guild.name}'."
)
except discord.errors.Forbidden:
return "Error: I don't have permission to delete."
except Exception as exc:
return f"Error deleting channel: {exc}"
return f"Error: Unknown action '{action}'."