Source code for tools.discord_server_info

"""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)