Metadata-Version: 2.4
Name: discord-wizard
Version: 1.5.0rc0
Summary: A unified Discord.py utility library — events, rate limiting, UI, permissions, tasks, errors, simulation, and recording.
Author: Muneeb
License: MIT
Project-URL: Homepage, https://github.com/draganoxfool/discord-wizard
Project-URL: Repository, https://github.com/draganoxfool/discord-wizard
Classifier: Development Status :: 4 - Beta
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: Topic :: Communications :: Chat
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: discord.py>=2.0
Provides-Extra: postgres
Requires-Dist: asyncpg; extra == "postgres"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-asyncio; extra == "dev"
Requires-Dist: ruff>=0.1.0; extra == "dev"

# discord-wizard

A batteries-included utility layer for discord.py. **Zero `import discord`** — all 318+ discord.py types re-exported through `wizard.*`.

```python
from discord.wizard import wizard   # convenience singleton
# or
from discord.wizard import Wizard   # class, if you prefer
w = Wizard()

bt = wizard.Bot(command_prefix="!")  # wizard.bot also works
wizard.enable_stability(bt)

@bt.slash()
async def hello(interaction: wizard.Interaction):
    await wizard.respond(interaction, "Hello!")
```

## Install

```bash
pip install discord-wizard
```

Requires Python 3.10+ and discord.py 2.x (installed automatically).

## Why no `import discord`?

Every public name from discord.py is accessible as `wizard.<Name>`:

```python
ChannelType, AppInfo, VoiceClient, FFmpegPCMAudio,
StageChannel, ForumChannel, Thread, DMChannel,
Permissions, MemberCacheFlags, AuditLogEntry,
Webhook, Sticker, Emoji, Template, Invite,
ScheduledEvent, AutoModRule, OnboardingPrompt,
Subscription, SKU, Entitlement, Poll, SoundboardSound,
...
```

All 318+ names. The only exception: wizard replaces `Bot` and `Cog` with its own enhanced versions (`wizard.Bot` / `wizard.Cog`).

## Quick Start

```python
from discord.wizard import wizard

bt = wizard.bot(command_prefix="!", intents=wizard.Intents.all())
wizard.enable_stability(bt)  # crash protection, auto-restart tasks, graceful shutdown


# ── Slash command with permission check ──────────────────────────────
@bt.slash()
@wizard.require("admin")
async def panel(interaction: wizard.Interaction):
    """Deploy a button panel."""
    p = wizard.Panel(title="Menu", buttons=[
        wizard.Button(label="Click me", style="primary",
                      callback=lambda i: wizard.respond(i, "Clicked!")),
    ])
    await p.send(interaction)


# ── Prefix command with type hints ──────────────────────────────────
@bt.cmd()
async def status(ctx, member: wizard.Member):
    e = wizard.embed(f"{member} Status", color="blue", fields=[
        ("ID", str(member.id)),
        ("Joined", str(member.joined_at)),
    ])
    await wizard.send(ctx, embed=e)


# ── Event with filter ──────────────────────────────────────────────
@wizard.on("member_join", guild_id=12345)
async def welcome(member):
    await member.send(f"Welcome {member.mention}!")


# ── Scheduled task ──────────────────────────────────────────────────
@wizard.every("30m")
async def clean_cache():
    print("Cache cleaned")


bt.run("TOKEN")
```

## Module Overview

### `wizard.on` — Event Decorators

Three syntaxes, same result:

```python
@wizard.on("member_join")           # string — simplest
@wizard.on.member_join              # flat attribute
@wizard.on.member.join              # grouped attribute

@wizard.on("member_join", guild_id=12345)  # with filter
```

Available events: `member_join/leave/ban/unban`, `message_create/delete/edit`, `voice_join/leave/move`, `role_create/delete/add`, `reaction_add/remove`.

### `wizard.require` — Permission Decorators

```python
@wizard.require("admin")               # built-in level
@wizard.require("role:Admin")          # role name
@wizard.require_any("mod", "admin")    # any of
@wizard.require_all("admin", "owner")  # all of
@wizard.require_custom(lambda m: m.id == 123)
wizard.configure_permissions(guild_id, roles={"admin": ["Admin", "Staff"]})
```

### `wizard.ui` — Declarative UI Components

```python
# Form (modal)
form = wizard.Form(title="Feedback", fields=[
    wizard.TextInput(label="Name", max_length=50),
    wizard.TextInput(label="Message", style="long"),
], on_submit=callback)
await form.send(interaction)

# Panel (button group)
panel = wizard.Panel(title="Actions", buttons=[
    wizard.Button(label="Save", style="success", callback=save_fn),
    wizard.Button(label="Delete", style="danger", callback=del_fn),
])
await panel.send(interaction)        # respond to interaction
await panel.send_to(channel, embed=e)  # send to channel

# Other components: Dropdown, MultiSelect, Tabs, Paginator
```

### `wizard.tasks` — Task Scheduling

```python
@wizard.every("5m")
@wizard.cron("0 */6 * * *")
@wizard.at("09:00", timezone="US/Eastern")
# retry=3, retry_delay=10, on_error=handler
```

### `wizard.embed` — One-Shot Embed Builder

```python
e = wizard.embed("Title", "Description", color="green", fields=[
    ("Name", "Value", True),   # (name, value, inline)
    ("Name2", "Value2"),       # inline defaults to False
])
```

Colors accept strings: `"blue"`, `"green"`, `"red"`, `"#5865F2"`, `0x5865F2`.

### `wizard.respond` / `wizard.send` — Unified Message Helpers

```python
await wizard.respond(interaction, "Hello", ephemeral=True)  # auto followup
await wizard.send(channel, embed=e)                          # channel or ctx
await wizard.send(interaction, "Hello")                      # also works
```

### `wizard.rateguard` — Rate Limit Protection

```python
wizard.enable_rateguard(bt, warn_at=0.8, max_retries=3)
print(wizard.rateguard_stats())  # {requests_sent: 42, ...}
```

### `wizard.errors` — Error Formatting

```python
wizard.enable_errors(bt, embed_mode=True)  # formats errors as embeds
print(wizard.format_terminal(error))         # structured terminal output
```

### `wizard.stability` — Crash Protection (one-call)

```python
wizard.enable_stability(bt)
# Crash-proof dispatch, safe tree.on_error, task auto-restart,
# connection health monitoring, graceful SIGINT/SIGTERM shutdown
```

### `wizard.simulate` — Offline Bot Testing

```bash
discord-wizard simulate --port 8321  # web UI at localhost:8321
```

```python
from discord.wizard import wizard

async def test():
    client = wizard.MockClient()
    result = await client.send("!hello")
    print(result.calls)
```

### `wizard.recorder` — Event Recording

```python
rec = wizard.Recorder(backend=wizard.SQLiteStore("bot.db"))
session = await rec.session.start(name="test")
# ... bot runs ...
await rec.session.stop()
await rec.export_json("log.json")
```

Backends: `MemoryStore`, `SQLiteStore`, `PostgreSQLStore`.

### Discord.py Re-exports (all 318+)

**Every** public name from discord.py is accessible as `wizard.<Type>` — no `import discord` needed:

Channel types: `TextChannel`, `VoiceChannel`, `CategoryChannel`, `StageChannel`, `ForumChannel`, `Thread`, `DMChannel`, `GroupChannel`, `PartialMessageable`

Models: `Member`, `User`, `Guild`, `Role`, `Message`, `Interaction`, `Embed`, `Emoji`, `Sticker`, `StickerPack`, `Webhook`, `Template`, `Invite`, `AppInfo`, `PartialAppInfo`, `ClientUser`, `Team`, `TeamMember`, `BanEntry`, `AuditLogEntry`, `AuditLogDiff`, `ScheduledEvent`, `AutoModRule`, `AutoModTrigger`, `Onboarding`, `OnboardingPrompt`, `Poll`, `Entitlement`, `SKU`, `Subscription`, `SoundboardSound`

Enums: `ChannelType`, `VideoQualityMode`, `VoiceRegion`, `NotificationLevel`, `ContentFilter`, `VerificationLevel`, `MFALevel`, `NSFWLevel`, `PrivacyLevel`, `EntityType`, `EventStatus`, `StickerType`, `StickerFormatType`, `AppCommandType`, `InteractionType`, `ComponentType`, `ButtonStyle`, `TextStyle`, `MessageType`, `AuditLogAction`, `AuditLogActionCategory`, `AutoModRuleEventType`, `AutoModRuleTriggerType`, `ForumLayoutType`, `ForumOrderType`, `InviteTarget`, `Locale`, `SKUType`, `SubscriptionStatus`, `WebhookType`, `EntitlementType`, `EntitlementOwnerType`

Flags: `Permissions`, `PermissionOverwrite`, `Intents`, `MemberCacheFlags`, `MemberFlags`, `MessageFlags`, `ChannelFlags`, `SystemChannelFlags`, `ApplicationFlags`, `PublicUserFlags`, `UserFlags`, `RoleFlags`, `AttachmentFlags`, `SKUFlags`, `InviteFlags`, `EmbedFlags`

Exceptions: `DiscordException`, `HTTPException`, `Forbidden`, `NotFound`, `InvalidData`, `ClientException`, `LoginFailure`, `GatewayNotFound`, `ConnectionClosed`, `PrivilegedIntentsRequired`, `InteractionResponded`, `RateLimited`, `DiscordServerError`, `MissingApplicationID`, `FFmpegProcessError`

Voice: `VoiceClient`, `VoiceProtocol`, `VoiceState`, `FFmpegPCMAudio`, `FFmpegOpusAudio`, `PCMAudio`, `PCMVolumeTransformer`, `AudioSource`, `opus`

And everything else: `Color`, `Colour`, `File`, `Asset`, `Object`, `Activity`, `Game`, `Status`, `CustomActivity`, `Spotify`, `Streaming`, `SelectOption`, `View`, `Button`, `ActionRow`, `TextInput`, `Component`, `AllowedMentions`, `PartialMessage`, `MessageReference`, `Reaction`, `ShardInfo`, `SessionStartLimits`, `Widget`, `WelcomeScreen`, `Integration`, `BotIntegration`, `StreamIntegration`, `IntegrationAccount`, `IntegrationApplication`, `VoiceChannelEffect`, `SoundboardDefaultSound`, `Container`, `Collectible`, `RoleTags`, `RoleSubscriptionInfo`, `ThreadMember`, `StageInstance`, `GuildPreview`, `GuildProductPurchase`, `PrimaryGuild`, `UnfurledMediaItem`, `MediaGalleryItem`, `CallMessage`, `DeletedReferencedMessage`, `MessageInteraction`, `MessageInteractionMetadata`, `MessageSnapshot`, `MessageApplication`, `RawMemberRemoveEvent`, `RawMessageDeleteEvent`, `RawMessageUpdateEvent`, `RawBulkMessageDeleteEvent`, `RawReactionActionEvent`, `RawReactionClearEvent`, `RawReactionClearEmojiEvent`, `RawTypingEvent`, `RawPresenceUpdateEvent`, `RawIntegrationDeleteEvent`, `RawThreadDeleteEvent`, `RawThreadMembersUpdate`, `RawThreadUpdateEvent`, `RawPollVoteActionEvent`, `RawAppCommandPermissionsUpdateEvent`, `PartialEmoji`, `PartialIntegration`, `PartialInviteChannel`, `PartialInviteGuild`, `PartialWebhookChannel`, `PartialWebhookGuild`, `SyncWebhook`, `SyncWebhookMessage`, `BaseActivity`, `BaseSoundboardSound`, `CustomActivity`, `SpeakingState

## Syntax Comparison

| Pattern | Raw discord.py | Wizard |
|---------|---------------|--------|
| Event | `@bot.event` / `def on_member_join(m)` | `@wizard.on("member_join")` |
| Event filter | Custom check function | `@wizard.on("member_join", guild_id=123)` |
| Embed | 3+ lines | `wizard.embed("T", fields=[...], color="blue")` |
| Interaction response | Check + followup + send_message | `wizard.respond(i, "msg", ephemeral=True)` |
| Send (any target) | Type check + different methods | `wizard.send(target, embed=e)` |
| Color | `Color.blue()`, `Color.from_str()` | `color="blue"` or `color="#5865F2"` |
| Permissions | Custom decorator | `@wizard.require("admin")` |
| Task scheduling | `asyncio` + manual | `@wizard.every("5m")` |

## Import Styles

```python
from discord.wizard import wizard   # singleton — most common
wizard.Bot(command_prefix="!", intents=wizard.Intents.all())

from discord.wizard import Wizard   # class — explicit
w = Wizard()
w.Bot(command_prefix="!", intents=w.Intents.all())
```

Both are equivalent. The singleton `wizard` is pre-instantiated and ready to use.

## License

MIT
