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.
- raycord-0.1.0/.gitignore +10 -0
- raycord-0.1.0/LICENCE.txt +21 -0
- raycord-0.1.0/PKG-INFO +268 -0
- raycord-0.1.0/README.txt +219 -0
- raycord-0.1.0/docs/api-reference.md +333 -0
- raycord-0.1.0/docs/async.md +96 -0
- raycord-0.1.0/docs/cogs.md +120 -0
- raycord-0.1.0/docs/commands.md +271 -0
- raycord-0.1.0/docs/events.md +120 -0
- raycord-0.1.0/docs/guilds.md +166 -0
- raycord-0.1.0/docs/index.md +152 -0
- raycord-0.1.0/docs/ui.md +176 -0
- raycord-0.1.0/docs/voice.md +101 -0
- raycord-0.1.0/examples/bot.py +178 -0
- raycord-0.1.0/pyproject.toml +56 -0
- raycord-0.1.0/raycord/__init__.py +310 -0
- raycord-0.1.0/raycord/_callbacks.py +141 -0
- raycord-0.1.0/raycord/_state.py +74 -0
- raycord-0.1.0/raycord/_task.py +112 -0
- raycord-0.1.0/raycord/bot.py +297 -0
- raycord-0.1.0/raycord/checks.py +251 -0
- raycord-0.1.0/raycord/cogs.py +184 -0
- raycord-0.1.0/raycord/commands_api.py +501 -0
- raycord-0.1.0/raycord/errors.py +23 -0
- raycord-0.1.0/raycord/events.py +128 -0
- raycord-0.1.0/raycord/guild.py +268 -0
- raycord-0.1.0/raycord/intents.py +56 -0
- raycord-0.1.0/raycord/messages.py +142 -0
- raycord-0.1.0/raycord/py.typed +0 -0
- raycord-0.1.0/raycord/ui.py +379 -0
- raycord-0.1.0/raycord/voice.py +186 -0
- raycord-0.1.0/tests/test_raycord.py +312 -0
raycord-0.1.0/.gitignore
ADDED
|
@@ -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.
|
raycord-0.1.0/README.txt
ADDED
|
@@ -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.
|