"""Query Discord server information (info, channels, roles, members)."""
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_server_info"
TOOL_DESCRIPTION = (
"Retrieve information about a Discord server. "
"Actions: 'get_info' (server details), 'list_channels', "
"'list_roles', 'list_members'."
)
TOOL_PARAMETERS = {
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": [
"get_info",
"list_channels",
"list_roles",
"list_members",
],
"description": "The query action to perform.",
},
"server_id": {
"type": "string",
"description": "The Discord server (guild) ID.",
},
"limit": {
"type": "integer",
"description": (
"Max members to list (list_members only, "
"default 50, max 100)."
),
},
},
"required": ["action", "server_id"],
}
[docs]
async def run(
action: str,
server_id: str,
limit: int = 50,
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.
limit (int): Maximum number of items.
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
client = require_discord_client(ctx)
if isinstance(client, str):
return client
try:
server = client.get_guild(int(server_id))
except ValueError:
return f"Error: Invalid server ID format: '{server_id}'."
if not server:
return f"Error: Server with ID '{server_id}' not found."
if action == "get_info":
return _get_info(server)
if action == "list_channels":
return _list_channels(server)
if action == "list_roles":
return _list_roles(server)
if action == "list_members":
return _list_members(server, limit)
return f"Error: Unknown action '{action}'."
def _get_info(server) -> str:
"""Internal helper: get info.
Args:
server: The server value.
Returns:
str: Result string.
"""
lines = [
f"**Server Information for '{server.name}'**",
f"ID: {server.id}",
]
if server.owner:
lines.append(
f"Owner: {server.owner.name} (ID: {server.owner.id})"
)
else:
lines.append("Owner: Unknown")
lines += [
f"Member Count: {server.member_count}",
f"Created: {server.created_at:%Y-%m-%d %H:%M:%S UTC}",
"",
"**Channels:**",
f" Text: {len(server.text_channels)}",
f" Voice: {len(server.voice_channels)}",
f" Categories: {len(server.categories)}",
f" Total: {len(server.channels)}",
"",
f"Roles: {len(server.roles)}",
f"Verification Level: {server.verification_level}",
]
if server.description:
lines += ["", f"Description: {server.description}"]
if hasattr(server, "premium_tier") and server.premium_tier:
lines.append(f"Boost Level: {server.premium_tier}")
return "\n".join(lines)
def _list_channels(server) -> str:
"""Internal helper: list channels.
Args:
server: The server value.
Returns:
str: Result string.
"""
import discord
lines = [f"**Channels in '{server.name}'**"]
categories: dict[str, list] = {}
uncategorized = []
for ch in server.channels:
if isinstance(ch, discord.CategoryChannel):
continue
if ch.category:
categories.setdefault(ch.category.name, []).append(ch)
else:
uncategorized.append(ch)
for cat in sorted(server.categories, key=lambda c: c.position):
chs = categories.get(cat.name, [])
if not chs:
continue
lines.append(f"\n**{cat.name}**")
for ch in sorted(chs, key=lambda c: c.position):
ctype = "text" if isinstance(ch, discord.TextChannel) \
else "voice" if isinstance(ch, discord.VoiceChannel) \
else "other"
lines.append(f" {ctype}: {ch.name} (ID: {ch.id})")
if uncategorized:
lines.append("\n**Uncategorized**")
for ch in sorted(uncategorized, key=lambda c: c.position):
ctype = "text" if isinstance(ch, discord.TextChannel) \
else "voice" if isinstance(ch, discord.VoiceChannel) \
else "other"
lines.append(f" {ctype}: {ch.name} (ID: {ch.id})")
return "\n".join(lines)
def _list_roles(server) -> str: # noqa: C901
"""Internal helper: list roles.
Args:
server: The server value.
Returns:
str: Result string.
"""
lines = [f"**Roles in '{server.name}'**"]
for role in sorted(
server.roles, key=lambda r: r.position, reverse=True,
):
props = []
if role.color.value != 0:
props.append(f"#{role.color.value:06X}")
if role.hoist:
props.append("Hoisted")
if role.mentionable:
props.append("Mentionable")
extra = f" ({', '.join(props)})" if props else ""
lines.append(f" {role.name} (ID: {role.id}){extra}")
return "\n".join(lines)
def _list_members(server, limit: int) -> str:
"""Internal helper: list members.
Args:
server: The server value.
limit (int): Maximum number of items.
Returns:
str: Result string.
"""
limit = max(1, min(limit, 100))
members = list(server.members)[:limit]
members.sort(
key=lambda m: m.joined_at or server.created_at,
)
lines = [
f"**Members in '{server.name}'** "
f"(showing {len(members)} of {server.member_count})"
]
for m in members:
roles = ", ".join(r.name for r in m.roles[1:])
extra = f" [{roles}]" if roles else ""
lines.append(f" {m.name}{extra}")
remaining = server.member_count - len(members)
if remaining > 0:
lines.append(f"\n... and {remaining} more members")
return "\n".join(lines)