Metadata-Version: 2.4
Name: Disquerd
Version: 0.1.0
Summary: A modern, beautiful Discord bot library for Python 3.10+
Author: disquerd contributors
License: MIT
Project-URL: Homepage, https://github.com/anomalyco/disquerd
Project-URL: Repository, https://github.com/anomalyco/disquerd
Project-URL: Documentation, https://github.com/anomalyco/disquerd/blob/main/DOCS.md
Keywords: discord,discord-bot,discord-api,slash-commands
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: aiohttp>=3.9
Requires-Dist: msgspec>=0.18
Provides-Extra: speed
Requires-Dist: orjson>=3.9; extra == "speed"
Requires-Dist: uvloop>=0.19; sys_platform != "win32" and extra == "speed"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
Requires-Dist: ruff>=0.1; extra == "dev"
Requires-Dist: mypy>=1.7; extra == "dev"

<p align="center">
  <img src="https://img.shields.io/pypi/v/disquerd?style=for-the-badge&color=5865F2" alt="PyPI">
  <img src="https://img.shields.io/pypi/pyversions/disquerd?style=for-the-badge&color=57F287" alt="Python">
  <img src="https://img.shields.io/pypi/l/disquerd?style=for-the-badge&color=EB459E" alt="License">
</p>

<h1 align="center">Disquerd</h1>

<p align="center">
  <strong>A modern, performant Discord bot library for Python 3.10+</strong><br>
  Built on Discord API v10 · msgspec-powered models · Full component support
</p>

---

## Features

- **Discord API v10** — no legacy cruft, only the latest API
- **msgspec Struct models** — zero-copy JSON decoding, strict typing, no silent field misses
- **Slash commands** — full support with options (USER, CHANNEL, ROLE, INTEGER, STRING, BOOLEAN, NUMBER, ATTACHMENT, MENTIONABLE)
- **Prefix commands** — with aliases, `shlex` argument parsing, `case_insensitive`, `strip_after_prefix`
- **Context menus** — user commands and message commands
- **Subcommands & groups** — `SubCommand`, `SubCommandGroup` for organized command trees
- **Autocomplete** — first-class autocomplete handler support
- **Components V1** — `View`, `Button`, `SelectMenu`, `ActionRow`, `Modal`, `TextInput`
- **Components V2** — `Container`, `Section`, `Separator`, `TextDisplay`, `MediaGallery`
- **Modal system** — `ModalBuilder`, `@bot.modal()` decorator, `ModalContext.value()`
- **Button listeners** — `@bot.button()` decorator, `ComponentContext` with `defer_update()`
- **Cog system** — `Cog.listener()` for event handlers inside cogs, auto-registration of commands
- **Command checks** — `is_owner`, `has_permissions`, `has_role`, `has_any_role`, `guild_only`, `dm_only`, `nsfw`, `cooldown`
- **Global checks** — `@bot.check` for prefix-wide validation
- **Invoke hooks** — `@bot.before_invoke` / `@bot.after_invoke`
- **Error events** — `SLASH_COMMAND_ERROR`, `COMMAND_ERROR`, `COMPONENT_ERROR`, `MODAL_ERROR`
- **Resolved objects** — `ctx.get_user()`, `ctx.get_channel()`, `ctx.get_role()` from interaction `resolved` data
- **Moderation** — `bot.mute()`, `bot.unmute()`, `bot.set_nickname()`, `bot.set_slowmode()`, `bot.purge_messages()`
- **Rich embeds** — `Embed` with fluent builder API, `embed()` shorthand, `ctx.send_success()` / `ctx.send_error()`
- **Confirm dialogs** — `ctx.confirm()` with `ConfirmView` (auto-responds to interactions)
- **Intents system** — bit-flag `Intents` class with `ALL`, `DEFAULT`, `ALL_PRIVILEGED`
- **Beautiful logging** — ANSI-colored formatter, startup banner with bot name/guilds/commands/latency
- **Lazy imports** — `__getattr__`-based, no circular dependency issues
- **Rate limiting** — per-bucket rate limit handling with `X-RateLimit-*` header awareness
- **zlib-stream** — full zlib-stream gateway decompression with session-persistent decompressor
- **Test mode** — interactive CLI REPL for local testing
- **CLI** — `disquerd init <name>` scaffolding

---

## Installation

```bash
pip install disquerd
```

For development:

```bash
pip install disquerd[dev]
```

For performance extras:

```bash
pip install disquerd[speed]
```

---

## Quick Start

### Minimal Bot

```python
import disquerd

bot = disquerd.Bot("YOUR_TOKEN")

@bot.slash_command(name="ping", description="Check latency")
async def ping(ctx):
    await ctx.respond("Pong!")

bot.run()
```

### Full-Featured Bot

```python
import disquerd

bot = disquerd.Bot(
    "YOUR_TOKEN",
    intents=disquerd.Intents.default(),
    prefix=["!", "?"],
    case_insensitive=True,
    strip_after_prefix=True,
)

# ── Slash command with options ──

@bot.slash_command(
    name="avatar",
    description="Show user avatar",
    options=[
        {"type": 6, "name": "user", "description": "Which user", "required": False},
    ],
)
async def avatar(ctx):
    target = ctx.get_user("user")
    url = getattr(target, "avatar_url", None)
    if not url:
        return await ctx.send_error("No avatar")
    e = disquerd.Embed(title="Avatar", colour=0x5865F2).set_image(url)
    await ctx.respond(embed=e)


# ── Prefix command with aliases ──

@bot.command(name="hello", aliases=["hi", "hey"])
async def hello(ctx):
    await ctx.reply(f"Hello, <@{ctx.author_id}>!")


# ── Modal with builder ──

@bot.slash_command(name="feedback", description="Send feedback")
async def feedback(ctx):
    m = disquerd.ModalBuilder(title="Feedback", custom_id="fb")
    m.add_text("topic", "Topic", placeholder="What about?")
    m.add_text("body", "Body", style=2, max_length=1000)
    await ctx.show_modal(m.build())


@bot.modal(custom_id="fb")
async def on_feedback(ctx):
    topic = ctx.value("topic", "No topic")
    body = ctx.value("body", "Empty")
    await ctx.respond(f"Thanks! **{topic}**: {body[:200]}")


# ── Button listener ──

@bot.button(custom_id="click_me")
async def on_click(ctx):
    await ctx.update_message(content="Clicked!")


# ── Cog with listener ──

class MyCog(disquerd.Cog):
    @disquerd.Cog.listener(disquerd.EventType.GUILD_MEMBER_ADD)
    async def on_member_join(self, member):
        gid = getattr(member, "guild_id", None)
        if gid:
            await self.bot.rest.create_message(
                channel_id=123456789,
                content=f"Welcome <@{getattr(member, 'id', '?')}>!",
            )

bot.add_cog(MyCog(bot))


# ── Hooks ──

@bot.before_invoke
async def log_cmd(ctx):
    print(f"Command by {ctx.author_id}")

@bot.check
async def global_check(ctx):
    return True


# ── Events ──

@bot.event(disquerd.EventType.READY)
async def on_ready():
    await bot.change_presence(activity={"name": "with disquerd", "type": 0})


@bot.event(disquerd.EventType.SLASH_COMMAND_ERROR)
async def on_slash_err(ctx, error):
    await ctx.send_error(str(error))


bot.run()
```

---

## Option Types Reference

When defining slash command `options`, use these `type` values:

| Type | Value | Description |
|------|-------|-------------|
| `STRING` | 3 | Text input |
| `INTEGER` | 4 | Whole number |
| `BOOLEAN` | 5 | True/false |
| `USER` | 6 | User (with `resolved`) |
| `CHANNEL` | 7 | Channel (with `resolved`) |
| `ROLE` | 8 | Role (with `resolved`) |
| `MENTIONABLE` | 9 | User or role |
| `NUMBER` | 10 | Floating-point number |
| `ATTACHMENT` | 11 | File attachment |

Access resolved objects in command handler:
- `ctx.get_user("option_name")` — returns `Member` or `User`
- `ctx.get_channel("option_name")` — returns `Channel`
- `ctx.get_role("option_name")` — returns `Role`

---

## Event Types

All `EventType` enum values available for `@bot.event()`:

| Event | Description |
|-------|-------------|
| `READY` | Bot connected and ready |
| `RESUMED` | Gateway session resumed |
| `MESSAGE_CREATE` | New message received |
| `MESSAGE_UPDATE` | Message edited |
| `MESSAGE_DELETE` | Message deleted |
| `INTERACTION_CREATE` | Any interaction |
| `GUILD_CREATE` | Bot joined / guild available |
| `GUILD_UPDATE` | Guild updated |
| `GUILD_DELETE` | Bot removed from guild |
| `GUILD_MEMBER_ADD` | Member joined |
| `GUILD_MEMBER_UPDATE` | Member updated |
| `GUILD_MEMBER_REMOVE` | Member left/kicked |
| `GUILD_ROLE_CREATE` | Role created |
| `GUILD_ROLE_UPDATE` | Role updated |
| `GUILD_ROLE_DELETE` | Role deleted |
| `CHANNEL_CREATE` | Channel created |
| `CHANNEL_UPDATE` | Channel updated |
| `CHANNEL_DELETE` | Channel deleted |
| `COMMAND` | Prefix command invoked |
| `COMMAND_COMPLETION` | Prefix command completed |
| `COMMAND_ERROR` | Prefix command errored |
| `SLASH_COMMAND_ERROR` | Slash command errored |
| `COMPONENT_ERROR` | Component interaction errored |
| `MODAL_ERROR` | Modal submit errored |

---

## Bot Properties

| Property | Type | Description |
|----------|------|-------------|
| `user` | `User \| None` | Bot's own user |
| `guilds` | `list[Guild]` | Cached guilds |
| `application_id` | `int \| None` | Application ID |
| `state` | `State` | Internal state/cache |
| `rest` | `RESTClient` | REST API client |
| `cogs` | `dict[str, Cog]` | Loaded cogs (copy) |
| `modals` | `dict[str, Modal]` | Registered modals (copy) |
| `button_listeners` | `dict[str, Callable]` | Button handlers (copy) |
| `username` | `str` | Bot's display name |
| `avatar_url` | `str \| None` | Bot's avatar URL |
| `mention` | `str` | Bot's mention string |
| `latency` | `float` | Heartbeat interval (seconds) |
| `latency_ms` | `float` | Heartbeat interval (milliseconds) |

---

## Architecture

```
disquerd/
├── client.py          # Bot, Intents
├── gateway.py         # Gateway v10: identify, resume, heartbeat, zlib-stream
├── rest.py            # RESTClient: all REST endpoints
├── state.py           # State: cache + msgspec parsing with ID coercion
├── _http.py           # HTTPClient: rate-limit-aware requests
├── _websocket.py      # WebSocketClient: zlib decompression
├── _registry.py       # Global REST registry (msgspec workaround)
├── _types.py          # Status, EventType, MISSING sentinel
├── log.py             # ANSI formatter, banner, logging setup
├── template.py        # quick(), use_template()
├── test_mode.py       # Interactive REPL test mode
├── commands/
│   ├── core.py        # CommandBase, CooldownMapping
│   ├── slash.py       # SlashCommand
│   ├── prefix.py      # PrefixCommand
│   ├── context.py     # ApplicationContext, ComponentContext, ModalContext, Context
│   ├── context_menu.py # UserCommand, MessageCommand
│   ├── subcommands.py  # SubCommand, SubCommandGroup
│   ├── autocomplete.py # autocomplete decorator
│   ├── checks.py      # is_owner, has_permissions, etc.
│   ├── cog.py         # Cog + Cog.listener()
│   └── converters.py  # UserConverter, ChannelConverter, etc.
├── models/
│   ├── user.py        # User
│   ├── guild.py       # Guild
│   ├── channel.py     # Channel, TextChannel, VoiceChannel, etc.
│   ├── message.py     # Message, Attachment
│   ├── member.py      # Member
│   ├── role.py        # Role
│   ├── emoji.py       # Emoji, PartialEmoji
│   ├── interaction.py # Interaction, InteractionType
│   ├── application.py # ApplicationCommand, ApplicationCommandOption
│   ├── component.py   # Component models
│   ├── extras.py      # AllowedMentions, Thread, Invite, Sticker, etc.
│   └── webhook.py     # Webhook
├── ui/
│   ├── view.py        # View
│   ├── button.py      # Button, ButtonStyle
│   ├── select.py      # SelectMenu, SelectOption
│   ├── modal.py       # Modal, ModalBuilder, TextInput, combine_text_inputs
│   ├── action_row.py  # ActionRow
│   └── components_v2.py # Container, Section, Separator, etc.
├── events/
│   └── dispatch.py    # EventDispatcher
├── errors/
│   ├── gateway.py     # GatewayError
│   ├── rest.py        # RESTError, HTTPError, RateLimitError, etc.
│   └── command.py     # CommandError, CommandCheckFailure, etc.
└── utils/
    ├── snowflake.py   # Snowflake
    ├── cache.py       # Cache, LRUCache, TTLCache
    ├── colour.py      # Colour (int subclass with named constructors)
    ├── embed.py       # Embed, embed() shorthand
    ├── permissions.py # Permissions
    ├── file.py        # File
    ├── pagination.py  # Paginator
    └── confirm.py     # ConfirmView
```

---

## Logging

By default, Disquerd is **silent** (WARNING+ level). Enable verbose output:

```python
disquerd.verbose_logging()
```

Or pass `debug=True` to `Bot()`:

```python
bot = disquerd.Bot("TOKEN", debug=True)
```

---

## CLI

```bash
# Scaffold a new bot project
disquerd init mybot

# Show version
disquerd version
```

---

## License

MIT
