Metadata-Version: 2.4
Name: pydiskit
Version: 2.0.0
Summary: A clean developer framework built on top of discord.py
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: discord.py<3,>=2.6
Provides-Extra: test
Requires-Dist: pytest>=8; extra == "test"
Requires-Dist: pytest-asyncio>=1; extra == "test"
Dynamic: license-file

# pydiskit

**pydiskit 2.0.0** is a small, typed-friendly framework built on top of [`discord.py`](https://discordpy.readthedocs.io/). It provides a compact `Bot`, prefix-command registration, slash-command registration, intent construction, safer dispatch, and a single application error hook.

## The reported error

The traceback

```text
ModuleNotFoundError: No module named 'pydiskit.slash'
```

usually means the installed distribution is incomplete or stale. This source tree already contains `pydiskit/slash/__init__.py`, `command.py`, and `manager.py`; the repair also makes the package version explicit and ensures the package finder includes all `pydiskit.*` subpackages.

Reinstall from a clean environment instead of mixing an old global install with this checkout:

```powershell
py -3.14 -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -e ".[test]"
python -c "import pydiskit; print(pydiskit.__file__)"
```

If the printed path is still under an unexpected `site-packages` directory, remove the stale copy and reinstall with the same interpreter:

```powershell
python -m pip uninstall -y pydiskit
python -m pip install -e ".[test]"
```

Always use `python -m pip`, not a bare `pip`, so that installation and execution use the same Python interpreter.

## Quick start

```python
import os
import logging

from pydiskit import Bot, Intents

logging.basicConfig(level=logging.INFO)

async def handle_error(error, context):
    logging.getLogger(__name__).exception("Command failed", exc_info=error)
    if hasattr(context, "send"):
        await context.send("Something went wrong while handling that command.")

bot = Bot(
    prefix="!",
    intents=Intents(guilds=True, messages=True, message_content=True),
    on_command_error=handle_error,
)

@bot.command()
async def ping(ctx):
    """Check whether the bot is responding."""
    await ctx.send("Pong!")

@bot.command()
async def say(ctx):
    """Echo the arguments, preserving quoted phrases."""
    if not ctx.args:
        await ctx.send("Usage: !say <text>")
        return
    await ctx.send(" ".join(ctx.args))

@bot.slash(description="Check whether the bot is responding.")
async def slash_ping(interaction):
    await interaction.response.send_message("Pong!")

bot.run(os.environ["DISCORD_TOKEN"])
```

## Handling model

Prefix messages are ignored when they come from another bot, do not start with the configured prefix, contain no command, or name an unregistered command. Arguments are parsed with `shlex`, so `!say "hello world"` produces one argument containing `hello world`. A malformed quoted argument is routed to `on_command_error` rather than crashing the event loop.

Registered command callbacks must be `async def`. Exceptions raised by a command are routed to `on_command_error(error, context)`. When no handler is supplied, the exception is logged with its traceback. Exceptions raised by the error handler itself are also logged.

| Feature | Behavior |
|---|---|
| Duplicate prefix command | Raises `ValueError` during registration |
| Duplicate slash command | Raises `ValueError` during registration |
| Empty prefix or token | Raises `ValueError` with an actionable message |
| Synchronous command callback | Raises `TypeError` during registration |
| Unknown command | Ignored, allowing other bot listeners to coexist |
| Command exception | Sent to the configured async error handler |
| Slash synchronization | Runs in `setup_hook` unless `sync_commands=False` |

## Intents

Discord requires privileged intents to be enabled both in the Developer Portal and in code. Construct only the intents the bot needs:

```python
intents = Intents(
    guilds=True,
    messages=True,
    message_content=True,
)
bot = Bot(intents=intents)
```

`Intents` rejects misspelled intent names with `ValueError`. Its `.build()` method returns the underlying `discord.Intents` object, and `.discord` is provided as a property when direct access is needed.

## Slash commands

Slash handlers receive the native Discord interaction object. They can therefore use Discord's normal response and follow-up APIs:

```python
@bot.slash(name="status", description="Show bot status")
async def status(interaction):
    await interaction.response.send_message("Online")
```

Commands are synchronized automatically by default. For offline tests or applications that manage synchronization themselves, construct `Bot(..., sync_commands=False)` and call `await bot.slash_commands.sync()` when appropriate.

## Debugging checklist

First confirm interpreter identity and package location:

```powershell
python --version
python -c "import sys, pydiskit; print(sys.executable); print(pydiskit.__file__)"
python -m pip show pydiskit
```

Then confirm that the installed package contains the slash package:

```powershell
python -c "import pydiskit.slash; print(pydiskit.slash.__file__)"
```

If that import fails after an editable install, check that the checkout contains this structure:

```text
pydiskit/
├── __init__.py
├── bot/
├── commands/
├── intents/
└── slash/
    ├── __init__.py
    ├── command.py
    └── manager.py
```

Do not create files with names such as `_*init*_.py`; Python requires the exact filename `__init__.py`. The traceback in the report displays escaped or transformed underscores, which is another reason to inspect the actual installed path rather than relying only on the visual traceback.

For runtime problems, enable logs before `bot.run`:

```python
import logging
logging.basicConfig(level=logging.DEBUG)
```

Never commit a bot token. Store it in an environment variable and rotate it immediately if it was exposed.

## Examples

The `examples/` directory contains runnable patterns for a basic bot, validation and error handling, quoted arguments, and an offline command-dispatch test. Install the project with `python -m pip install -e ".[test]"`, set `DISCORD_TOKEN`, and run an example with `python examples/basic.py`.

## Development and tests

```powershell
python -m pip install -e ".[test]"
python -m compileall pydiskit examples
pytest -q
```

## License

See [LICENSE](LICENSE).
