raycord 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ .mypy_cache/
7
+ build/
8
+ dist/
9
+ *.egg-info/
10
+ .env
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 raycord contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
raycord-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,268 @@
1
+ Metadata-Version: 2.5
2
+ Name: raycord
3
+ Version: 0.1.0
4
+ Summary: A raylib-simple wrapper around discord.py for building Discord bots.
5
+ Project-URL: Homepage, https://github.com/raycord/raycord
6
+ Author: raycord contributors
7
+ License: MIT License
8
+
9
+ Copyright (c) 2026 raycord contributors
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+ License-File: LICENCE.txt
29
+ Keywords: async,bot,discord,discord.py,raylib,wrapper
30
+ Classifier: Development Status :: 3 - Alpha
31
+ Classifier: Framework :: AsyncIO
32
+ Classifier: Intended Audience :: Developers
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Programming Language :: Python :: 3
35
+ Classifier: Programming Language :: Python :: 3.9
36
+ Classifier: Programming Language :: Python :: 3.10
37
+ Classifier: Programming Language :: Python :: 3.11
38
+ Classifier: Programming Language :: Python :: 3.12
39
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
40
+ Requires-Python: >=3.9
41
+ Requires-Dist: discord-py>=2.4
42
+ Provides-Extra: dev
43
+ Requires-Dist: mypy>=1.10; extra == 'dev'
44
+ Requires-Dist: pytest>=7.0; extra == 'dev'
45
+ Requires-Dist: ruff>=0.5; extra == 'dev'
46
+ Provides-Extra: voice
47
+ Requires-Dist: pynacl>=1.5; extra == 'voice'
48
+ Description-Content-Type: text/plain
49
+
50
+ raycord
51
+ =======
52
+
53
+ A raylib-simple wrapper around discord.py. One global bot, plain functions,
54
+ and almost no ceremony:
55
+
56
+ import raycord
57
+
58
+ raycord.init("YOUR_TOKEN", prefix="!")
59
+
60
+ @raycord.on_ready
61
+ def ready():
62
+ print("online as", raycord.user())
63
+
64
+ @raycord.command("ping")
65
+ def ping(ctx):
66
+ raycord.reply(ctx, "pong")
67
+
68
+ raycord.run()
69
+
70
+ Nothing needs `await`. Every helper starts its discord.py coroutine right
71
+ away and returns a RayTask, so you can:
72
+
73
+ raycord.send(channel, "hi") # fire and forget
74
+ raycord.send(channel, "hi").result() # block from sync code
75
+ await raycord.send(channel, "hi") # or await from async code
76
+
77
+ Under it all is the real discord.py: if raycord does not wrap something, reach
78
+ for `raycord.discord`, `raycord.commands` or `raycord.app_commands`, or the bot
79
+ itself via `raycord.bot()`.
80
+
81
+
82
+ Install
83
+ -------
84
+
85
+ pip install discord.py # raycord's only hard dependency
86
+ pip install PyNaCl # only for voice
87
+
88
+ Copy the `raycord/` folder into your project, or `pip install .` from this
89
+ directory. Python 3.9+.
90
+
91
+
92
+ Documentation
93
+ -------------
94
+
95
+ Full guides live in `docs/`:
96
+
97
+ docs/index.md overview, install, first bot, concepts
98
+ docs/async.md RayTask and sync vs async handlers
99
+ docs/commands.md prefix, slash, groups, context menus, checks, cooldowns
100
+ docs/events.md event decorators and error handling
101
+ docs/ui.md buttons, select menus, modals, persistent views
102
+ docs/voice.md voice channels and FFmpeg playback
103
+ docs/guilds.md channels, threads, roles, emoji, events, moderation
104
+ docs/cogs.md grouping code and hot reloading
105
+ docs/api-reference.md every public function and class
106
+
107
+ The quick version follows.
108
+
109
+
110
+ The shape of the API
111
+ --------------------
112
+
113
+ Lifecycle
114
+ init(token, prefix="!", intents="default", shard="none", sync=False)
115
+ run() / start() / launch()
116
+ shutdown() / stop(), reset()
117
+ bot(), user(), guilds(), get_guild(), get_channel(), get_user(), get_member()
118
+ is_ready(), wait_until_ready(), sync(guild=None), set_status(...)
119
+
120
+ Events
121
+ @on_ready, @on_message, @on_member_join, @on_reaction_add, ...
122
+ @on("thread_create") # any discord.py event
123
+ @on_command_error, @on_app_error
124
+ add_listener(handler, "event")
125
+
126
+ Classic commands
127
+ @command("ping", aliases=["p"], help="pong")
128
+ def ping(ctx, member: discord.Member):
129
+ reply(ctx, "pong")
130
+
131
+ Slash commands
132
+ @slash("hello", description="say hi", describe={"name": "your name"})
133
+ def hello(interaction, name: str = "world"):
134
+ reply(interaction, f"hi {name}")
135
+
136
+ admin = slash_group("admin", "admin tools")
137
+
138
+ @admin.command("ban")
139
+ def ban(interaction, member: discord.Member):
140
+ reply(interaction, "banned")
141
+
142
+ Context menus
143
+ @user_command("High Five")
144
+ def high_five(interaction, member):
145
+ reply(interaction, "hi")
146
+
147
+ @message_command("Report")
148
+ def report(interaction, message):
149
+ reply(interaction, "reported")
150
+
151
+ Checks and cooldowns (work on prefix and slash alike)
152
+ @command("ban")
153
+ @cooldown(1, 5, "user")
154
+ @has_permissions(ban_members=True)
155
+ def ban(ctx, member: discord.Member): ...
156
+
157
+ also: is_owner(), guild_only(), dm_only(), nsfw_only(), bot_has_permissions(),
158
+ has_role(), has_any_role(), check(predicate)
159
+
160
+ UI: buttons, menus, modals
161
+ @command("menu")
162
+ def menu(ctx):
163
+ reply(ctx, "Pick one", view=view(
164
+ button("Yes", "yes", style="success"),
165
+ button("No", "no", style="danger"),
166
+ ))
167
+
168
+ @on_button("yes")
169
+ def said_yes(interaction):
170
+ reply(interaction, "yes!")
171
+
172
+ @on_select("pick")
173
+ def picked(interaction, values):
174
+ reply(interaction, values)
175
+
176
+ form = modal("Feedback", [field("Name"), field("Message", style="long")], id="fb")
177
+ show_modal(interaction, form)
178
+
179
+ @on_modal("fb")
180
+ def submitted(interaction, values):
181
+ reply(interaction, values["Name"])
182
+
183
+ Messages
184
+ send(target, content, view=..., embed=..., ephemeral=True)
185
+ reply(target, content, ...)
186
+ edit(target, content=...), delete(target), react(target, emoji)
187
+ pin(target), dm(user, content), fetch_message(channel, id)
188
+
189
+ Voice (needs FFmpeg on PATH and PyNaCl installed)
190
+ join_voice(ctx), leave_voice(ctx)
191
+ play(ctx, "song.mp3", volume=0.5, wait=True)
192
+ stop_voice(), pause_voice(), resume_voice(), is_playing()
193
+
194
+ Guild management
195
+ create_text_channel(guild, "name"), create_voice_channel(...),
196
+ create_category(...), create_forum(...), create_channel(guild, "name", kind="forum")
197
+ create_thread(channel, "name"), create_forum_post(forum, "title", "body")
198
+ create_role(guild, "Mod", color=0x00FF00, hoist=True)
199
+ create_emoji(guild, "name", image_bytes), create_sticker(guild, "name", file)
200
+ create_event(guild, "Movie night", start_time, channel=vc)
201
+ edit(obj, **fields), delete(obj), kick(member), ban(user), unban(guild, user)
202
+
203
+ Cogs (reload a file without restarting the bot)
204
+ class Admin(raycord.Cog):
205
+ @command("ban")
206
+ def ban(self, ctx, member: discord.Member):
207
+ reply(ctx, "banned")
208
+
209
+ @slash("kick")
210
+ def kick(self, interaction, member: discord.Member):
211
+ reply(interaction, "kicked")
212
+
213
+ @on_message
214
+ def watch(self, message):
215
+ ...
216
+
217
+ add_cog(Admin())
218
+ remove_cog(Admin)
219
+ reload_cog(Admin) # re-imports the module and swaps the cog
220
+
221
+ Intents
222
+ init(token, intents="default") # guilds + messages + message_content
223
+ intents="minimal" # no privileged intents
224
+ intents="all" # everything (enable in the dev portal)
225
+ intents("default", members=True) # tweak specific flags
226
+
227
+ Privileged intents (message content, members, presence) must also be turned
228
+ on in the Discord Developer Portal.
229
+
230
+ Scaling
231
+ init(token, shard="auto") # discord.py's AutoShardedBot
232
+
233
+ Slash commands
234
+ Run sync() once after changing them. During development, sync to one guild
235
+ with sync(guild=some_guild) — it is instant, unlike global sync.
236
+
237
+
238
+ How the raylib feel works
239
+ -------------------------
240
+
241
+ * One implicit context, like raylib's window: `raycord.init` creates it and the
242
+ decorators attach to it. `raycord.reset()` clears it.
243
+ * Functions are verbs, not objects: `send`, `reply`, `button`, `play`, `ban`.
244
+ * No `await` tax, no passing the bot around, no cogs required. Cogs are there
245
+ when a project grows.
246
+ * Automatic rate limiting, auto-sharding, converters, checks and the whole
247
+ discord.py ecosystem still apply underneath.
248
+
249
+
250
+ Notes and limits
251
+ ----------------
252
+
253
+ * A `view(...)` with the default `timeout=None` is registered as a persistent
254
+ view, so buttons on old messages keep working after a restart, as long as the
255
+ same `@on_button` / `@on_select` handlers run at startup.
256
+ * Plain `def` handlers run on the event loop, so do not block in them. When you
257
+ need a return value, write the handler as `async def` and `await` the RayTask
258
+ (`thread = await create_thread(channel, "hi")`). `.result()` is meant for code
259
+ outside the loop (scripts, threads) and raises if called on the loop thread.
260
+ * `reload_cog` re-imports the whole module, so keep cog classes at module level.
261
+ * Voice needs `ffmpeg` installed and `PyNaCl` for the underlying voice lib.
262
+ * Tests: `python -m pytest`.
263
+
264
+
265
+ License
266
+ -------
267
+
268
+ MIT. See LICENCE.txt.
@@ -0,0 +1,219 @@
1
+ raycord
2
+ =======
3
+
4
+ A raylib-simple wrapper around discord.py. One global bot, plain functions,
5
+ and almost no ceremony:
6
+
7
+ import raycord
8
+
9
+ raycord.init("YOUR_TOKEN", prefix="!")
10
+
11
+ @raycord.on_ready
12
+ def ready():
13
+ print("online as", raycord.user())
14
+
15
+ @raycord.command("ping")
16
+ def ping(ctx):
17
+ raycord.reply(ctx, "pong")
18
+
19
+ raycord.run()
20
+
21
+ Nothing needs `await`. Every helper starts its discord.py coroutine right
22
+ away and returns a RayTask, so you can:
23
+
24
+ raycord.send(channel, "hi") # fire and forget
25
+ raycord.send(channel, "hi").result() # block from sync code
26
+ await raycord.send(channel, "hi") # or await from async code
27
+
28
+ Under it all is the real discord.py: if raycord does not wrap something, reach
29
+ for `raycord.discord`, `raycord.commands` or `raycord.app_commands`, or the bot
30
+ itself via `raycord.bot()`.
31
+
32
+
33
+ Install
34
+ -------
35
+
36
+ pip install discord.py # raycord's only hard dependency
37
+ pip install PyNaCl # only for voice
38
+
39
+ Copy the `raycord/` folder into your project, or `pip install .` from this
40
+ directory. Python 3.9+.
41
+
42
+
43
+ Documentation
44
+ -------------
45
+
46
+ Full guides live in `docs/`:
47
+
48
+ docs/index.md overview, install, first bot, concepts
49
+ docs/async.md RayTask and sync vs async handlers
50
+ docs/commands.md prefix, slash, groups, context menus, checks, cooldowns
51
+ docs/events.md event decorators and error handling
52
+ docs/ui.md buttons, select menus, modals, persistent views
53
+ docs/voice.md voice channels and FFmpeg playback
54
+ docs/guilds.md channels, threads, roles, emoji, events, moderation
55
+ docs/cogs.md grouping code and hot reloading
56
+ docs/api-reference.md every public function and class
57
+
58
+ The quick version follows.
59
+
60
+
61
+ The shape of the API
62
+ --------------------
63
+
64
+ Lifecycle
65
+ init(token, prefix="!", intents="default", shard="none", sync=False)
66
+ run() / start() / launch()
67
+ shutdown() / stop(), reset()
68
+ bot(), user(), guilds(), get_guild(), get_channel(), get_user(), get_member()
69
+ is_ready(), wait_until_ready(), sync(guild=None), set_status(...)
70
+
71
+ Events
72
+ @on_ready, @on_message, @on_member_join, @on_reaction_add, ...
73
+ @on("thread_create") # any discord.py event
74
+ @on_command_error, @on_app_error
75
+ add_listener(handler, "event")
76
+
77
+ Classic commands
78
+ @command("ping", aliases=["p"], help="pong")
79
+ def ping(ctx, member: discord.Member):
80
+ reply(ctx, "pong")
81
+
82
+ Slash commands
83
+ @slash("hello", description="say hi", describe={"name": "your name"})
84
+ def hello(interaction, name: str = "world"):
85
+ reply(interaction, f"hi {name}")
86
+
87
+ admin = slash_group("admin", "admin tools")
88
+
89
+ @admin.command("ban")
90
+ def ban(interaction, member: discord.Member):
91
+ reply(interaction, "banned")
92
+
93
+ Context menus
94
+ @user_command("High Five")
95
+ def high_five(interaction, member):
96
+ reply(interaction, "hi")
97
+
98
+ @message_command("Report")
99
+ def report(interaction, message):
100
+ reply(interaction, "reported")
101
+
102
+ Checks and cooldowns (work on prefix and slash alike)
103
+ @command("ban")
104
+ @cooldown(1, 5, "user")
105
+ @has_permissions(ban_members=True)
106
+ def ban(ctx, member: discord.Member): ...
107
+
108
+ also: is_owner(), guild_only(), dm_only(), nsfw_only(), bot_has_permissions(),
109
+ has_role(), has_any_role(), check(predicate)
110
+
111
+ UI: buttons, menus, modals
112
+ @command("menu")
113
+ def menu(ctx):
114
+ reply(ctx, "Pick one", view=view(
115
+ button("Yes", "yes", style="success"),
116
+ button("No", "no", style="danger"),
117
+ ))
118
+
119
+ @on_button("yes")
120
+ def said_yes(interaction):
121
+ reply(interaction, "yes!")
122
+
123
+ @on_select("pick")
124
+ def picked(interaction, values):
125
+ reply(interaction, values)
126
+
127
+ form = modal("Feedback", [field("Name"), field("Message", style="long")], id="fb")
128
+ show_modal(interaction, form)
129
+
130
+ @on_modal("fb")
131
+ def submitted(interaction, values):
132
+ reply(interaction, values["Name"])
133
+
134
+ Messages
135
+ send(target, content, view=..., embed=..., ephemeral=True)
136
+ reply(target, content, ...)
137
+ edit(target, content=...), delete(target), react(target, emoji)
138
+ pin(target), dm(user, content), fetch_message(channel, id)
139
+
140
+ Voice (needs FFmpeg on PATH and PyNaCl installed)
141
+ join_voice(ctx), leave_voice(ctx)
142
+ play(ctx, "song.mp3", volume=0.5, wait=True)
143
+ stop_voice(), pause_voice(), resume_voice(), is_playing()
144
+
145
+ Guild management
146
+ create_text_channel(guild, "name"), create_voice_channel(...),
147
+ create_category(...), create_forum(...), create_channel(guild, "name", kind="forum")
148
+ create_thread(channel, "name"), create_forum_post(forum, "title", "body")
149
+ create_role(guild, "Mod", color=0x00FF00, hoist=True)
150
+ create_emoji(guild, "name", image_bytes), create_sticker(guild, "name", file)
151
+ create_event(guild, "Movie night", start_time, channel=vc)
152
+ edit(obj, **fields), delete(obj), kick(member), ban(user), unban(guild, user)
153
+
154
+ Cogs (reload a file without restarting the bot)
155
+ class Admin(raycord.Cog):
156
+ @command("ban")
157
+ def ban(self, ctx, member: discord.Member):
158
+ reply(ctx, "banned")
159
+
160
+ @slash("kick")
161
+ def kick(self, interaction, member: discord.Member):
162
+ reply(interaction, "kicked")
163
+
164
+ @on_message
165
+ def watch(self, message):
166
+ ...
167
+
168
+ add_cog(Admin())
169
+ remove_cog(Admin)
170
+ reload_cog(Admin) # re-imports the module and swaps the cog
171
+
172
+ Intents
173
+ init(token, intents="default") # guilds + messages + message_content
174
+ intents="minimal" # no privileged intents
175
+ intents="all" # everything (enable in the dev portal)
176
+ intents("default", members=True) # tweak specific flags
177
+
178
+ Privileged intents (message content, members, presence) must also be turned
179
+ on in the Discord Developer Portal.
180
+
181
+ Scaling
182
+ init(token, shard="auto") # discord.py's AutoShardedBot
183
+
184
+ Slash commands
185
+ Run sync() once after changing them. During development, sync to one guild
186
+ with sync(guild=some_guild) — it is instant, unlike global sync.
187
+
188
+
189
+ How the raylib feel works
190
+ -------------------------
191
+
192
+ * One implicit context, like raylib's window: `raycord.init` creates it and the
193
+ decorators attach to it. `raycord.reset()` clears it.
194
+ * Functions are verbs, not objects: `send`, `reply`, `button`, `play`, `ban`.
195
+ * No `await` tax, no passing the bot around, no cogs required. Cogs are there
196
+ when a project grows.
197
+ * Automatic rate limiting, auto-sharding, converters, checks and the whole
198
+ discord.py ecosystem still apply underneath.
199
+
200
+
201
+ Notes and limits
202
+ ----------------
203
+
204
+ * A `view(...)` with the default `timeout=None` is registered as a persistent
205
+ view, so buttons on old messages keep working after a restart, as long as the
206
+ same `@on_button` / `@on_select` handlers run at startup.
207
+ * Plain `def` handlers run on the event loop, so do not block in them. When you
208
+ need a return value, write the handler as `async def` and `await` the RayTask
209
+ (`thread = await create_thread(channel, "hi")`). `.result()` is meant for code
210
+ outside the loop (scripts, threads) and raises if called on the loop thread.
211
+ * `reload_cog` re-imports the whole module, so keep cog classes at module level.
212
+ * Voice needs `ffmpeg` installed and `PyNaCl` for the underlying voice lib.
213
+ * Tests: `python -m pytest`.
214
+
215
+
216
+ License
217
+ -------
218
+
219
+ MIT. See LICENCE.txt.