Metadata-Version: 2.5
Name: raycord
Version: 0.1.0
Summary: A raylib-simple wrapper around discord.py for building Discord bots.
Project-URL: Homepage, https://github.com/raycord/raycord
Author: raycord contributors
License: MIT License
        
        Copyright (c) 2026 raycord contributors
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENCE.txt
Keywords: async,bot,discord,discord.py,raylib,wrapper
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.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Requires-Dist: discord-py>=2.4
Provides-Extra: dev
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Provides-Extra: voice
Requires-Dist: pynacl>=1.5; extra == 'voice'
Description-Content-Type: text/plain

raycord
=======

A raylib-simple wrapper around discord.py.  One global bot, plain functions,
and almost no ceremony:

    import raycord

    raycord.init("YOUR_TOKEN", prefix="!")

    @raycord.on_ready
    def ready():
        print("online as", raycord.user())

    @raycord.command("ping")
    def ping(ctx):
        raycord.reply(ctx, "pong")

    raycord.run()

Nothing needs `await`.  Every helper starts its discord.py coroutine right
away and returns a RayTask, so you can:

    raycord.send(channel, "hi")                 # fire and forget
    raycord.send(channel, "hi").result()        # block from sync code
    await raycord.send(channel, "hi")           # or await from async code

Under it all is the real discord.py: if raycord does not wrap something, reach
for `raycord.discord`, `raycord.commands` or `raycord.app_commands`, or the bot
itself via `raycord.bot()`.


Install
-------

    pip install discord.py      # raycord's only hard dependency
    pip install PyNaCl          # only for voice

Copy the `raycord/` folder into your project, or `pip install .` from this
directory.  Python 3.9+.


Documentation
-------------

Full guides live in `docs/`:

    docs/index.md           overview, install, first bot, concepts
    docs/async.md           RayTask and sync vs async handlers
    docs/commands.md        prefix, slash, groups, context menus, checks, cooldowns
    docs/events.md          event decorators and error handling
    docs/ui.md              buttons, select menus, modals, persistent views
    docs/voice.md           voice channels and FFmpeg playback
    docs/guilds.md          channels, threads, roles, emoji, events, moderation
    docs/cogs.md            grouping code and hot reloading
    docs/api-reference.md   every public function and class

The quick version follows.


The shape of the API
--------------------

Lifecycle
    init(token, prefix="!", intents="default", shard="none", sync=False)
    run() / start() / launch()
    shutdown() / stop(), reset()
    bot(), user(), guilds(), get_guild(), get_channel(), get_user(), get_member()
    is_ready(), wait_until_ready(), sync(guild=None), set_status(...)

Events
    @on_ready, @on_message, @on_member_join, @on_reaction_add, ...
    @on("thread_create")                      # any discord.py event
    @on_command_error, @on_app_error
    add_listener(handler, "event")

Classic commands
    @command("ping", aliases=["p"], help="pong")
    def ping(ctx, member: discord.Member):
        reply(ctx, "pong")

Slash commands
    @slash("hello", description="say hi", describe={"name": "your name"})
    def hello(interaction, name: str = "world"):
        reply(interaction, f"hi {name}")

    admin = slash_group("admin", "admin tools")

    @admin.command("ban")
    def ban(interaction, member: discord.Member):
        reply(interaction, "banned")

Context menus
    @user_command("High Five")
    def high_five(interaction, member):
        reply(interaction, "hi")

    @message_command("Report")
    def report(interaction, message):
        reply(interaction, "reported")

Checks and cooldowns (work on prefix and slash alike)
    @command("ban")
    @cooldown(1, 5, "user")
    @has_permissions(ban_members=True)
    def ban(ctx, member: discord.Member): ...

    also: is_owner(), guild_only(), dm_only(), nsfw_only(), bot_has_permissions(),
          has_role(), has_any_role(), check(predicate)

UI: buttons, menus, modals
    @command("menu")
    def menu(ctx):
        reply(ctx, "Pick one", view=view(
            button("Yes", "yes", style="success"),
            button("No", "no", style="danger"),
        ))

    @on_button("yes")
    def said_yes(interaction):
        reply(interaction, "yes!")

    @on_select("pick")
    def picked(interaction, values):
        reply(interaction, values)

    form = modal("Feedback", [field("Name"), field("Message", style="long")], id="fb")
    show_modal(interaction, form)

    @on_modal("fb")
    def submitted(interaction, values):
        reply(interaction, values["Name"])

Messages
    send(target, content, view=..., embed=..., ephemeral=True)
    reply(target, content, ...)
    edit(target, content=...), delete(target), react(target, emoji)
    pin(target), dm(user, content), fetch_message(channel, id)

Voice (needs FFmpeg on PATH and PyNaCl installed)
    join_voice(ctx), leave_voice(ctx)
    play(ctx, "song.mp3", volume=0.5, wait=True)
    stop_voice(), pause_voice(), resume_voice(), is_playing()

Guild management
    create_text_channel(guild, "name"), create_voice_channel(...),
    create_category(...), create_forum(...), create_channel(guild, "name", kind="forum")
    create_thread(channel, "name"), create_forum_post(forum, "title", "body")
    create_role(guild, "Mod", color=0x00FF00, hoist=True)
    create_emoji(guild, "name", image_bytes), create_sticker(guild, "name", file)
    create_event(guild, "Movie night", start_time, channel=vc)
    edit(obj, **fields), delete(obj), kick(member), ban(user), unban(guild, user)

Cogs (reload a file without restarting the bot)
    class Admin(raycord.Cog):
        @command("ban")
        def ban(self, ctx, member: discord.Member):
            reply(ctx, "banned")

        @slash("kick")
        def kick(self, interaction, member: discord.Member):
            reply(interaction, "kicked")

        @on_message
        def watch(self, message):
            ...

    add_cog(Admin())
    remove_cog(Admin)
    reload_cog(Admin)      # re-imports the module and swaps the cog

Intents
    init(token, intents="default")   # guilds + messages + message_content
    intents="minimal"                # no privileged intents
    intents="all"                    # everything (enable in the dev portal)
    intents("default", members=True) # tweak specific flags

    Privileged intents (message content, members, presence) must also be turned
    on in the Discord Developer Portal.

Scaling
    init(token, shard="auto")        # discord.py's AutoShardedBot

Slash commands
    Run sync() once after changing them.  During development, sync to one guild
    with sync(guild=some_guild) — it is instant, unlike global sync.


How the raylib feel works
-------------------------

* One implicit context, like raylib's window: `raycord.init` creates it and the
  decorators attach to it.  `raycord.reset()` clears it.
* Functions are verbs, not objects: `send`, `reply`, `button`, `play`, `ban`.
* No `await` tax, no passing the bot around, no cogs required.  Cogs are there
  when a project grows.
* Automatic rate limiting, auto-sharding, converters, checks and the whole
  discord.py ecosystem still apply underneath.


Notes and limits
----------------

* A `view(...)` with the default `timeout=None` is registered as a persistent
  view, so buttons on old messages keep working after a restart, as long as the
  same `@on_button` / `@on_select` handlers run at startup.
* Plain `def` handlers run on the event loop, so do not block in them.  When you
  need a return value, write the handler as `async def` and `await` the RayTask
  (`thread = await create_thread(channel, "hi")`).  `.result()` is meant for code
  outside the loop (scripts, threads) and raises if called on the loop thread.
* `reload_cog` re-imports the whole module, so keep cog classes at module level.
* Voice needs `ffmpeg` installed and `PyNaCl` for the underlying voice lib.
* Tests: `python -m pytest`.


License
-------

MIT.  See LICENCE.txt.
