shoal 0.1.0a1__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.
- shoal-0.1.0a1/.gitignore +28 -0
- shoal-0.1.0a1/CHANGELOG.md +241 -0
- shoal-0.1.0a1/LICENSE +21 -0
- shoal-0.1.0a1/PKG-INFO +218 -0
- shoal-0.1.0a1/README.md +173 -0
- shoal-0.1.0a1/docs/examples/README.md +64 -0
- shoal-0.1.0a1/docs/examples/bigbot/README.md +79 -0
- shoal-0.1.0a1/docs/examples/clusters/README.md +56 -0
- shoal-0.1.0a1/docs/examples/cogs/README.md +27 -0
- shoal-0.1.0a1/docs/examples/compat/README.md +37 -0
- shoal-0.1.0a1/docs/examples/components/README.md +26 -0
- shoal-0.1.0a1/docs/examples/emojis/README.md +23 -0
- shoal-0.1.0a1/docs/examples/forms/README.md +28 -0
- shoal-0.1.0a1/docs/examples/giveaways/README.md +24 -0
- shoal-0.1.0a1/docs/examples/hello/README.md +16 -0
- shoal-0.1.0a1/docs/examples/http/README.md +31 -0
- shoal-0.1.0a1/docs/examples/hybrid/README.md +22 -0
- shoal-0.1.0a1/docs/examples/leveling/README.md +25 -0
- shoal-0.1.0a1/docs/examples/logs/README.md +22 -0
- shoal-0.1.0a1/docs/examples/moderation/README.md +23 -0
- shoal-0.1.0a1/docs/examples/paginator/README.md +22 -0
- shoal-0.1.0a1/docs/examples/polls/README.md +21 -0
- shoal-0.1.0a1/docs/examples/prefix/README.md +25 -0
- shoal-0.1.0a1/docs/examples/reminders/README.md +24 -0
- shoal-0.1.0a1/docs/examples/rest/README.md +21 -0
- shoal-0.1.0a1/docs/examples/roles/buttons/README.md +24 -0
- shoal-0.1.0a1/docs/examples/roles/reactions/README.md +23 -0
- shoal-0.1.0a1/docs/examples/slash/README.md +26 -0
- shoal-0.1.0a1/docs/examples/starboard/README.md +21 -0
- shoal-0.1.0a1/docs/examples/tickets/README.md +26 -0
- shoal-0.1.0a1/docs/examples/voice/messages/README.md +26 -0
- shoal-0.1.0a1/docs/examples/voice/music/README.md +27 -0
- shoal-0.1.0a1/docs/examples/voice/recorder/README.md +26 -0
- shoal-0.1.0a1/docs/examples/welcome/README.md +21 -0
- shoal-0.1.0a1/docs/examples/workers/README.md +62 -0
- shoal-0.1.0a1/pyproject.toml +267 -0
- shoal-0.1.0a1/src/shoal/__init__.py +122 -0
- shoal-0.1.0a1/src/shoal/_version.py +3 -0
- shoal-0.1.0a1/src/shoal/app_commands/__init__.py +144 -0
- shoal-0.1.0a1/src/shoal/app_commands/checks.py +145 -0
- shoal-0.1.0a1/src/shoal/app_commands/commands.py +1069 -0
- shoal-0.1.0a1/src/shoal/app_commands/errors.py +152 -0
- shoal-0.1.0a1/src/shoal/app_commands/models.py +96 -0
- shoal-0.1.0a1/src/shoal/app_commands/parameters.py +374 -0
- shoal-0.1.0a1/src/shoal/app_commands/transformers.py +114 -0
- shoal-0.1.0a1/src/shoal/app_commands/translator.py +125 -0
- shoal-0.1.0a1/src/shoal/app_commands/tree.py +585 -0
- shoal-0.1.0a1/src/shoal/cache/__init__.py +33 -0
- shoal-0.1.0a1/src/shoal/cache/collection.py +269 -0
- shoal-0.1.0a1/src/shoal/cache/defaults.py +21 -0
- shoal-0.1.0a1/src/shoal/cache/entry.py +45 -0
- shoal-0.1.0a1/src/shoal/cache/guild.py +22 -0
- shoal-0.1.0a1/src/shoal/cache/policy.py +105 -0
- shoal-0.1.0a1/src/shoal/cache/store.py +776 -0
- shoal-0.1.0a1/src/shoal/cache/trim.py +66 -0
- shoal-0.1.0a1/src/shoal/cache/updater.py +820 -0
- shoal-0.1.0a1/src/shoal/client/__init__.py +36 -0
- shoal-0.1.0a1/src/shoal/client/client.py +1355 -0
- shoal-0.1.0a1/src/shoal/client/defaults.py +21 -0
- shoal-0.1.0a1/src/shoal/client/events.py +579 -0
- shoal-0.1.0a1/src/shoal/client/interaction.py +634 -0
- shoal-0.1.0a1/src/shoal/client/views.py +186 -0
- shoal-0.1.0a1/src/shoal/commands/__init__.py +311 -0
- shoal-0.1.0a1/src/shoal/commands/bot.py +663 -0
- shoal-0.1.0a1/src/shoal/commands/checks.py +401 -0
- shoal-0.1.0a1/src/shoal/commands/cog.py +257 -0
- shoal-0.1.0a1/src/shoal/commands/context.py +301 -0
- shoal-0.1.0a1/src/shoal/commands/converters.py +807 -0
- shoal-0.1.0a1/src/shoal/commands/core.py +855 -0
- shoal-0.1.0a1/src/shoal/commands/errors.py +513 -0
- shoal-0.1.0a1/src/shoal/commands/flags.py +473 -0
- shoal-0.1.0a1/src/shoal/commands/help.py +442 -0
- shoal-0.1.0a1/src/shoal/commands/hybrid.py +326 -0
- shoal-0.1.0a1/src/shoal/commands/parameters.py +540 -0
- shoal-0.1.0a1/src/shoal/commands/view.py +133 -0
- shoal-0.1.0a1/src/shoal/compat/__init__.py +441 -0
- shoal-0.1.0a1/src/shoal/compat/app_commands.py +162 -0
- shoal-0.1.0a1/src/shoal/compat/commands.py +416 -0
- shoal-0.1.0a1/src/shoal/compat/utils.py +141 -0
- shoal-0.1.0a1/src/shoal/core/__init__.py +55 -0
- shoal-0.1.0a1/src/shoal/core/backoff.py +22 -0
- shoal-0.1.0a1/src/shoal/core/clock.py +84 -0
- shoal-0.1.0a1/src/shoal/core/codec.py +165 -0
- shoal-0.1.0a1/src/shoal/core/cooldown.py +162 -0
- shoal-0.1.0a1/src/shoal/core/defaults.py +9 -0
- shoal-0.1.0a1/src/shoal/core/dispatch.py +50 -0
- shoal-0.1.0a1/src/shoal/core/enums.py +52 -0
- shoal-0.1.0a1/src/shoal/core/errors.py +15 -0
- shoal-0.1.0a1/src/shoal/core/files.py +57 -0
- shoal-0.1.0a1/src/shoal/core/flags.py +169 -0
- shoal-0.1.0a1/src/shoal/core/formatting.py +126 -0
- shoal-0.1.0a1/src/shoal/core/metrics.py +338 -0
- shoal-0.1.0a1/src/shoal/core/ogg.py +270 -0
- shoal-0.1.0a1/src/shoal/core/replies.py +15 -0
- shoal-0.1.0a1/src/shoal/core/snowflake.py +113 -0
- shoal-0.1.0a1/src/shoal/core/tasks.py +45 -0
- shoal-0.1.0a1/src/shoal/diagnostics/__init__.py +11 -0
- shoal-0.1.0a1/src/shoal/diagnostics/defaults.py +9 -0
- shoal-0.1.0a1/src/shoal/diagnostics/loop.py +222 -0
- shoal-0.1.0a1/src/shoal/emojis/__init__.py +34 -0
- shoal-0.1.0a1/src/shoal/emojis/__main__.py +74 -0
- shoal-0.1.0a1/src/shoal/emojis/defaults.py +17 -0
- shoal-0.1.0a1/src/shoal/emojis/registry.py +271 -0
- shoal-0.1.0a1/src/shoal/emojis/sync.py +379 -0
- shoal-0.1.0a1/src/shoal/endpoint/__init__.py +15 -0
- shoal-0.1.0a1/src/shoal/endpoint/defaults.py +19 -0
- shoal-0.1.0a1/src/shoal/endpoint/server.py +330 -0
- shoal-0.1.0a1/src/shoal/endpoint/signature.py +59 -0
- shoal-0.1.0a1/src/shoal/gateway/__init__.py +82 -0
- shoal-0.1.0a1/src/shoal/gateway/cluster.py +194 -0
- shoal-0.1.0a1/src/shoal/gateway/compression.py +133 -0
- shoal-0.1.0a1/src/shoal/gateway/connection.py +127 -0
- shoal-0.1.0a1/src/shoal/gateway/defaults.py +68 -0
- shoal-0.1.0a1/src/shoal/gateway/heartbeat.py +68 -0
- shoal-0.1.0a1/src/shoal/gateway/manager.py +573 -0
- shoal-0.1.0a1/src/shoal/gateway/ratelimit.py +51 -0
- shoal-0.1.0a1/src/shoal/gateway/session.py +154 -0
- shoal-0.1.0a1/src/shoal/gateway/shard.py +907 -0
- shoal-0.1.0a1/src/shoal/gateway/sharding.py +130 -0
- shoal-0.1.0a1/src/shoal/ipc/__init__.py +17 -0
- shoal-0.1.0a1/src/shoal/ipc/client.py +285 -0
- shoal-0.1.0a1/src/shoal/ipc/defaults.py +26 -0
- shoal-0.1.0a1/src/shoal/ipc/protocol.py +137 -0
- shoal-0.1.0a1/src/shoal/ipc/server.py +328 -0
- shoal-0.1.0a1/src/shoal/metrics/__init__.py +17 -0
- shoal-0.1.0a1/src/shoal/metrics/prometheus.py +96 -0
- shoal-0.1.0a1/src/shoal/models/__init__.py +102 -0
- shoal-0.1.0a1/src/shoal/models/activity.py +42 -0
- shoal-0.1.0a1/src/shoal/models/asset.py +185 -0
- shoal-0.1.0a1/src/shoal/models/base.py +81 -0
- shoal-0.1.0a1/src/shoal/models/channel.py +892 -0
- shoal-0.1.0a1/src/shoal/models/defaults.py +11 -0
- shoal-0.1.0a1/src/shoal/models/embed.py +244 -0
- shoal-0.1.0a1/src/shoal/models/emoji.py +261 -0
- shoal-0.1.0a1/src/shoal/models/guild.py +849 -0
- shoal-0.1.0a1/src/shoal/models/member.py +400 -0
- shoal-0.1.0a1/src/shoal/models/message.py +677 -0
- shoal-0.1.0a1/src/shoal/models/messageable.py +298 -0
- shoal-0.1.0a1/src/shoal/models/permissions.py +146 -0
- shoal-0.1.0a1/src/shoal/models/role.py +242 -0
- shoal-0.1.0a1/src/shoal/models/sending.py +314 -0
- shoal-0.1.0a1/src/shoal/models/state.py +231 -0
- shoal-0.1.0a1/src/shoal/models/thread.py +280 -0
- shoal-0.1.0a1/src/shoal/models/user.py +224 -0
- shoal-0.1.0a1/src/shoal/models/voice.py +464 -0
- shoal-0.1.0a1/src/shoal/payloads/__init__.py +1352 -0
- shoal-0.1.0a1/src/shoal/payloads/api.py +177 -0
- shoal-0.1.0a1/src/shoal/payloads/application.py +332 -0
- shoal-0.1.0a1/src/shoal/payloads/application_commands.py +366 -0
- shoal-0.1.0a1/src/shoal/payloads/application_identity.py +152 -0
- shoal-0.1.0a1/src/shoal/payloads/audit_log.py +200 -0
- shoal-0.1.0a1/src/shoal/payloads/auto_moderation.py +160 -0
- shoal-0.1.0a1/src/shoal/payloads/base.py +81 -0
- shoal-0.1.0a1/src/shoal/payloads/channel.py +541 -0
- shoal-0.1.0a1/src/shoal/payloads/components.py +968 -0
- shoal-0.1.0a1/src/shoal/payloads/emoji.py +145 -0
- shoal-0.1.0a1/src/shoal/payloads/errors.py +78 -0
- shoal-0.1.0a1/src/shoal/payloads/forum.py +58 -0
- shoal-0.1.0a1/src/shoal/payloads/gateway.py +268 -0
- shoal-0.1.0a1/src/shoal/payloads/gateway_events.py +1177 -0
- shoal-0.1.0a1/src/shoal/payloads/guild.py +901 -0
- shoal-0.1.0a1/src/shoal/payloads/guild_template.py +58 -0
- shoal-0.1.0a1/src/shoal/payloads/interactions.py +543 -0
- shoal-0.1.0a1/src/shoal/payloads/invite.py +231 -0
- shoal-0.1.0a1/src/shoal/payloads/lobby.py +207 -0
- shoal-0.1.0a1/src/shoal/payloads/message.py +897 -0
- shoal-0.1.0a1/src/shoal/payloads/monetization.py +182 -0
- shoal-0.1.0a1/src/shoal/payloads/permissions.py +149 -0
- shoal-0.1.0a1/src/shoal/payloads/poll.py +123 -0
- shoal-0.1.0a1/src/shoal/payloads/rate_limits.py +72 -0
- shoal-0.1.0a1/src/shoal/payloads/scheduled_event.py +244 -0
- shoal-0.1.0a1/src/shoal/payloads/soundboard.py +80 -0
- shoal-0.1.0a1/src/shoal/payloads/stage_instance.py +64 -0
- shoal-0.1.0a1/src/shoal/payloads/sticker.py +123 -0
- shoal-0.1.0a1/src/shoal/payloads/user.py +260 -0
- shoal-0.1.0a1/src/shoal/payloads/voice.py +76 -0
- shoal-0.1.0a1/src/shoal/payloads/voice_gateway.py +362 -0
- shoal-0.1.0a1/src/shoal/payloads/webhook.py +171 -0
- shoal-0.1.0a1/src/shoal/payloads/webhook_events.py +177 -0
- shoal-0.1.0a1/src/shoal/py.typed +0 -0
- shoal-0.1.0a1/src/shoal/redis/__init__.py +48 -0
- shoal-0.1.0a1/src/shoal/redis/base.py +29 -0
- shoal-0.1.0a1/src/shoal/redis/cache.py +314 -0
- shoal-0.1.0a1/src/shoal/redis/cluster.py +154 -0
- shoal-0.1.0a1/src/shoal/redis/defaults.py +32 -0
- shoal-0.1.0a1/src/shoal/redis/events.py +309 -0
- shoal-0.1.0a1/src/shoal/redis/identify.py +84 -0
- shoal-0.1.0a1/src/shoal/redis/jobs.py +222 -0
- shoal-0.1.0a1/src/shoal/redis/ratelimit.py +327 -0
- shoal-0.1.0a1/src/shoal/redis/sessions.py +68 -0
- shoal-0.1.0a1/src/shoal/rest/__init__.py +85 -0
- shoal-0.1.0a1/src/shoal/rest/client.py +549 -0
- shoal-0.1.0a1/src/shoal/rest/defaults.py +74 -0
- shoal-0.1.0a1/src/shoal/rest/endpoints.py +1690 -0
- shoal-0.1.0a1/src/shoal/rest/errors.py +181 -0
- shoal-0.1.0a1/src/shoal/rest/files.py +291 -0
- shoal-0.1.0a1/src/shoal/rest/proxy.py +330 -0
- shoal-0.1.0a1/src/shoal/rest/ratelimit.py +517 -0
- shoal-0.1.0a1/src/shoal/rest/requester.py +285 -0
- shoal-0.1.0a1/src/shoal/rest/resources/__init__.py +64 -0
- shoal-0.1.0a1/src/shoal/rest/resources/application.py +159 -0
- shoal-0.1.0a1/src/shoal/rest/resources/application_commands.py +249 -0
- shoal-0.1.0a1/src/shoal/rest/resources/application_identity.py +128 -0
- shoal-0.1.0a1/src/shoal/rest/resources/audit_log.py +28 -0
- shoal-0.1.0a1/src/shoal/rest/resources/auto_moderation.py +91 -0
- shoal-0.1.0a1/src/shoal/rest/resources/channel.py +394 -0
- shoal-0.1.0a1/src/shoal/rest/resources/emoji.py +139 -0
- shoal-0.1.0a1/src/shoal/rest/resources/gateway.py +34 -0
- shoal-0.1.0a1/src/shoal/rest/resources/guild.py +545 -0
- shoal-0.1.0a1/src/shoal/rest/resources/guild_template.py +78 -0
- shoal-0.1.0a1/src/shoal/rest/resources/interactions.py +239 -0
- shoal-0.1.0a1/src/shoal/rest/resources/invite.py +194 -0
- shoal-0.1.0a1/src/shoal/rest/resources/lobby.py +202 -0
- shoal-0.1.0a1/src/shoal/rest/resources/message.py +322 -0
- shoal-0.1.0a1/src/shoal/rest/resources/monetization.py +130 -0
- shoal-0.1.0a1/src/shoal/rest/resources/scheduled_event.py +113 -0
- shoal-0.1.0a1/src/shoal/rest/resources/soundboard.py +115 -0
- shoal-0.1.0a1/src/shoal/rest/resources/stage_instance.py +59 -0
- shoal-0.1.0a1/src/shoal/rest/resources/sticker.py +118 -0
- shoal-0.1.0a1/src/shoal/rest/resources/user.py +79 -0
- shoal-0.1.0a1/src/shoal/rest/resources/voice.py +76 -0
- shoal-0.1.0a1/src/shoal/rest/resources/webhook.py +307 -0
- shoal-0.1.0a1/src/shoal/rest/route.py +150 -0
- shoal-0.1.0a1/src/shoal/rest/transport.py +131 -0
- shoal-0.1.0a1/src/shoal/tasks/__init__.py +32 -0
- shoal-0.1.0a1/src/shoal/tasks/cron.py +156 -0
- shoal-0.1.0a1/src/shoal/tasks/defaults.py +22 -0
- shoal-0.1.0a1/src/shoal/tasks/jobs.py +182 -0
- shoal-0.1.0a1/src/shoal/tasks/loop.py +303 -0
- shoal-0.1.0a1/src/shoal/tasks/scheduler.py +269 -0
- shoal-0.1.0a1/src/shoal/ui/__init__.py +72 -0
- shoal-0.1.0a1/src/shoal/ui/action_row.py +243 -0
- shoal-0.1.0a1/src/shoal/ui/button.py +144 -0
- shoal-0.1.0a1/src/shoal/ui/content.py +220 -0
- shoal-0.1.0a1/src/shoal/ui/defaults.py +6 -0
- shoal-0.1.0a1/src/shoal/ui/inputs.py +317 -0
- shoal-0.1.0a1/src/shoal/ui/item.py +323 -0
- shoal-0.1.0a1/src/shoal/ui/label.py +89 -0
- shoal-0.1.0a1/src/shoal/ui/layout.py +247 -0
- shoal-0.1.0a1/src/shoal/ui/lifetime.py +106 -0
- shoal-0.1.0a1/src/shoal/ui/modal.py +265 -0
- shoal-0.1.0a1/src/shoal/ui/select.py +559 -0
- shoal-0.1.0a1/src/shoal/ui/text_input.py +118 -0
- shoal-0.1.0a1/src/shoal/ui/view.py +417 -0
- shoal-0.1.0a1/src/shoal/voice/__init__.py +41 -0
- shoal-0.1.0a1/src/shoal/voice/audio.py +296 -0
- shoal-0.1.0a1/src/shoal/voice/connection.py +645 -0
- shoal-0.1.0a1/src/shoal/voice/crypto.py +137 -0
- shoal-0.1.0a1/src/shoal/voice/dave.py +263 -0
- shoal-0.1.0a1/src/shoal/voice/defaults.py +54 -0
- shoal-0.1.0a1/src/shoal/voice/receive/__init__.py +38 -0
- shoal-0.1.0a1/src/shoal/voice/receive/opus.py +224 -0
- shoal-0.1.0a1/src/shoal/voice/receive/stream.py +199 -0
- shoal-0.1.0a1/src/shoal/voice/rtp.py +100 -0
- shoal-0.1.0a1/src/shoal/voice/udp.py +166 -0
- shoal-0.1.0a1/tests/__init__.py +1 -0
- shoal-0.1.0a1/tests/app_commands/__init__.py +1 -0
- shoal-0.1.0a1/tests/app_commands/checks.py +147 -0
- shoal-0.1.0a1/tests/app_commands/commands.py +483 -0
- shoal-0.1.0a1/tests/app_commands/errors.py +26 -0
- shoal-0.1.0a1/tests/app_commands/models.py +37 -0
- shoal-0.1.0a1/tests/app_commands/parameters.py +197 -0
- shoal-0.1.0a1/tests/app_commands/transformers.py +167 -0
- shoal-0.1.0a1/tests/app_commands/translator.py +186 -0
- shoal-0.1.0a1/tests/app_commands/tree.py +603 -0
- shoal-0.1.0a1/tests/cache/__init__.py +1 -0
- shoal-0.1.0a1/tests/cache/collection.py +112 -0
- shoal-0.1.0a1/tests/cache/fixtures/snapshot-4.msgpack +0 -0
- shoal-0.1.0a1/tests/cache/guild.py +19 -0
- shoal-0.1.0a1/tests/cache/policy.py +29 -0
- shoal-0.1.0a1/tests/cache/store.py +461 -0
- shoal-0.1.0a1/tests/cache/trim.py +97 -0
- shoal-0.1.0a1/tests/cache/updater.py +1025 -0
- shoal-0.1.0a1/tests/client/__init__.py +1 -0
- shoal-0.1.0a1/tests/client/client.py +1168 -0
- shoal-0.1.0a1/tests/client/events.py +497 -0
- shoal-0.1.0a1/tests/client/interaction.py +260 -0
- shoal-0.1.0a1/tests/client/views.py +132 -0
- shoal-0.1.0a1/tests/commands/__init__.py +1 -0
- shoal-0.1.0a1/tests/commands/bot.py +400 -0
- shoal-0.1.0a1/tests/commands/checks.py +176 -0
- shoal-0.1.0a1/tests/commands/cog.py +328 -0
- shoal-0.1.0a1/tests/commands/context.py +168 -0
- shoal-0.1.0a1/tests/commands/converters.py +339 -0
- shoal-0.1.0a1/tests/commands/core.py +432 -0
- shoal-0.1.0a1/tests/commands/errors.py +87 -0
- shoal-0.1.0a1/tests/commands/flags.py +228 -0
- shoal-0.1.0a1/tests/commands/help.py +190 -0
- shoal-0.1.0a1/tests/commands/hybrid.py +313 -0
- shoal-0.1.0a1/tests/commands/parameters.py +232 -0
- shoal-0.1.0a1/tests/commands/view.py +53 -0
- shoal-0.1.0a1/tests/compat/__init__.py +1 -0
- shoal-0.1.0a1/tests/compat/utils.py +93 -0
- shoal-0.1.0a1/tests/conventions.py +536 -0
- shoal-0.1.0a1/tests/core/__init__.py +1 -0
- shoal-0.1.0a1/tests/core/backoff.py +22 -0
- shoal-0.1.0a1/tests/core/clock.py +88 -0
- shoal-0.1.0a1/tests/core/codec.py +66 -0
- shoal-0.1.0a1/tests/core/cooldown.py +131 -0
- shoal-0.1.0a1/tests/core/dispatch.py +23 -0
- shoal-0.1.0a1/tests/core/enums.py +36 -0
- shoal-0.1.0a1/tests/core/errors.py +8 -0
- shoal-0.1.0a1/tests/core/files.py +33 -0
- shoal-0.1.0a1/tests/core/flags.py +96 -0
- shoal-0.1.0a1/tests/core/formatting.py +66 -0
- shoal-0.1.0a1/tests/core/metrics.py +48 -0
- shoal-0.1.0a1/tests/core/ogg.py +102 -0
- shoal-0.1.0a1/tests/core/snowflake.py +87 -0
- shoal-0.1.0a1/tests/core/tasks.py +50 -0
- shoal-0.1.0a1/tests/diagnostics/__init__.py +1 -0
- shoal-0.1.0a1/tests/diagnostics/loop.py +118 -0
- shoal-0.1.0a1/tests/emojis/__init__.py +1 -0
- shoal-0.1.0a1/tests/emojis/__main__.py +55 -0
- shoal-0.1.0a1/tests/emojis/registry.py +132 -0
- shoal-0.1.0a1/tests/emojis/sync.py +278 -0
- shoal-0.1.0a1/tests/endpoint/__init__.py +1 -0
- shoal-0.1.0a1/tests/endpoint/server.py +357 -0
- shoal-0.1.0a1/tests/endpoint/signature.py +45 -0
- shoal-0.1.0a1/tests/gateway/__init__.py +1 -0
- shoal-0.1.0a1/tests/gateway/cluster.py +70 -0
- shoal-0.1.0a1/tests/gateway/compression.py +55 -0
- shoal-0.1.0a1/tests/gateway/connection.py +78 -0
- shoal-0.1.0a1/tests/gateway/heartbeat.py +27 -0
- shoal-0.1.0a1/tests/gateway/manager.py +620 -0
- shoal-0.1.0a1/tests/gateway/ratelimit.py +20 -0
- shoal-0.1.0a1/tests/gateway/session.py +82 -0
- shoal-0.1.0a1/tests/gateway/shard.py +832 -0
- shoal-0.1.0a1/tests/gateway/sharding.py +64 -0
- shoal-0.1.0a1/tests/ipc/__init__.py +1 -0
- shoal-0.1.0a1/tests/ipc/client.py +98 -0
- shoal-0.1.0a1/tests/ipc/conftest.py +56 -0
- shoal-0.1.0a1/tests/ipc/protocol.py +73 -0
- shoal-0.1.0a1/tests/ipc/server.py +206 -0
- shoal-0.1.0a1/tests/metrics/__init__.py +1 -0
- shoal-0.1.0a1/tests/metrics/prometheus.py +49 -0
- shoal-0.1.0a1/tests/models/__init__.py +1 -0
- shoal-0.1.0a1/tests/models/activity.py +15 -0
- shoal-0.1.0a1/tests/models/asset.py +64 -0
- shoal-0.1.0a1/tests/models/base.py +36 -0
- shoal-0.1.0a1/tests/models/channel.py +348 -0
- shoal-0.1.0a1/tests/models/embed.py +99 -0
- shoal-0.1.0a1/tests/models/emoji.py +106 -0
- shoal-0.1.0a1/tests/models/guild.py +286 -0
- shoal-0.1.0a1/tests/models/member.py +203 -0
- shoal-0.1.0a1/tests/models/message.py +264 -0
- shoal-0.1.0a1/tests/models/messageable.py +189 -0
- shoal-0.1.0a1/tests/models/permissions.py +166 -0
- shoal-0.1.0a1/tests/models/role.py +89 -0
- shoal-0.1.0a1/tests/models/sending.py +172 -0
- shoal-0.1.0a1/tests/models/state.py +22 -0
- shoal-0.1.0a1/tests/models/thread.py +84 -0
- shoal-0.1.0a1/tests/models/user.py +99 -0
- shoal-0.1.0a1/tests/models/voice.py +352 -0
- shoal-0.1.0a1/tests/payloads/__init__.py +1 -0
- shoal-0.1.0a1/tests/payloads/api.py +28 -0
- shoal-0.1.0a1/tests/payloads/application_commands.py +129 -0
- shoal-0.1.0a1/tests/payloads/base.py +68 -0
- shoal-0.1.0a1/tests/payloads/channel.py +27 -0
- shoal-0.1.0a1/tests/payloads/components.py +216 -0
- shoal-0.1.0a1/tests/payloads/documented_examples.py +168 -0
- shoal-0.1.0a1/tests/payloads/emoji.py +33 -0
- shoal-0.1.0a1/tests/payloads/fixtures/discord_component_examples.json +667 -0
- shoal-0.1.0a1/tests/payloads/fixtures/discord_examples.json +481 -0
- shoal-0.1.0a1/tests/payloads/fixtures/discord_interaction_examples.json +561 -0
- shoal-0.1.0a1/tests/payloads/gateway.py +48 -0
- shoal-0.1.0a1/tests/payloads/gateway_events.py +146 -0
- shoal-0.1.0a1/tests/payloads/interactions.py +319 -0
- shoal-0.1.0a1/tests/payloads/message.py +32 -0
- shoal-0.1.0a1/tests/payloads/permissions.py +26 -0
- shoal-0.1.0a1/tests/payloads/rate_limits.py +11 -0
- shoal-0.1.0a1/tests/payloads/voice_gateway.py +55 -0
- shoal-0.1.0a1/tests/payloads/webhook_events.py +137 -0
- shoal-0.1.0a1/tests/redis/__init__.py +1 -0
- shoal-0.1.0a1/tests/redis/base.py +11 -0
- shoal-0.1.0a1/tests/redis/cache.py +149 -0
- shoal-0.1.0a1/tests/redis/cluster.py +79 -0
- shoal-0.1.0a1/tests/redis/conftest.py +67 -0
- shoal-0.1.0a1/tests/redis/events.py +212 -0
- shoal-0.1.0a1/tests/redis/identify.py +57 -0
- shoal-0.1.0a1/tests/redis/jobs.py +129 -0
- shoal-0.1.0a1/tests/redis/ratelimit.py +233 -0
- shoal-0.1.0a1/tests/redis/sessions.py +39 -0
- shoal-0.1.0a1/tests/rest/__init__.py +1 -0
- shoal-0.1.0a1/tests/rest/client.py +393 -0
- shoal-0.1.0a1/tests/rest/endpoints.py +119 -0
- shoal-0.1.0a1/tests/rest/errors.py +64 -0
- shoal-0.1.0a1/tests/rest/files.py +173 -0
- shoal-0.1.0a1/tests/rest/proxy.py +277 -0
- shoal-0.1.0a1/tests/rest/ratelimit.py +391 -0
- shoal-0.1.0a1/tests/rest/requester.py +106 -0
- shoal-0.1.0a1/tests/rest/resources/__init__.py +1 -0
- shoal-0.1.0a1/tests/rest/resources/application.py +135 -0
- shoal-0.1.0a1/tests/rest/resources/application_commands.py +159 -0
- shoal-0.1.0a1/tests/rest/resources/application_identity.py +121 -0
- shoal-0.1.0a1/tests/rest/resources/audit_log.py +71 -0
- shoal-0.1.0a1/tests/rest/resources/auto_moderation.py +86 -0
- shoal-0.1.0a1/tests/rest/resources/channel.py +321 -0
- shoal-0.1.0a1/tests/rest/resources/emoji.py +104 -0
- shoal-0.1.0a1/tests/rest/resources/gateway.py +32 -0
- shoal-0.1.0a1/tests/rest/resources/guild.py +632 -0
- shoal-0.1.0a1/tests/rest/resources/guild_template.py +74 -0
- shoal-0.1.0a1/tests/rest/resources/interactions.py +185 -0
- shoal-0.1.0a1/tests/rest/resources/invite.py +115 -0
- shoal-0.1.0a1/tests/rest/resources/lobby.py +191 -0
- shoal-0.1.0a1/tests/rest/resources/message.py +298 -0
- shoal-0.1.0a1/tests/rest/resources/monetization.py +111 -0
- shoal-0.1.0a1/tests/rest/resources/scheduled_event.py +105 -0
- shoal-0.1.0a1/tests/rest/resources/soundboard.py +93 -0
- shoal-0.1.0a1/tests/rest/resources/stage_instance.py +55 -0
- shoal-0.1.0a1/tests/rest/resources/sticker.py +104 -0
- shoal-0.1.0a1/tests/rest/resources/user.py +142 -0
- shoal-0.1.0a1/tests/rest/resources/voice.py +56 -0
- shoal-0.1.0a1/tests/rest/resources/webhook.py +204 -0
- shoal-0.1.0a1/tests/rest/route.py +100 -0
- shoal-0.1.0a1/tests/rest/transport.py +79 -0
- shoal-0.1.0a1/tests/scripts/__init__.py +1 -0
- shoal-0.1.0a1/tests/scripts/check_docs.py +81 -0
- shoal-0.1.0a1/tests/scripts/generate_exports.py +25 -0
- shoal-0.1.0a1/tests/scripts/mirror_upstream_prs.py +1159 -0
- shoal-0.1.0a1/tests/scripts/release_notes.py +57 -0
- shoal-0.1.0a1/tests/support/__init__.py +1 -0
- shoal-0.1.0a1/tests/support/calls.py +12 -0
- shoal-0.1.0a1/tests/support/client.py +101 -0
- shoal-0.1.0a1/tests/support/commands.py +110 -0
- shoal-0.1.0a1/tests/support/dispatches.py +285 -0
- shoal-0.1.0a1/tests/support/endpoint.py +27 -0
- shoal-0.1.0a1/tests/support/fakes.py +209 -0
- shoal-0.1.0a1/tests/support/interactions.py +223 -0
- shoal-0.1.0a1/tests/support/json_payloads.py +348 -0
- shoal-0.1.0a1/tests/support/models.py +350 -0
- shoal-0.1.0a1/tests/support/multipart.py +33 -0
- shoal-0.1.0a1/tests/support/redis.py +15 -0
- shoal-0.1.0a1/tests/support/rest.py +113 -0
- shoal-0.1.0a1/tests/support/server.py +29 -0
- shoal-0.1.0a1/tests/support/signing.py +12 -0
- shoal-0.1.0a1/tests/support/snapshot.py +66 -0
- shoal-0.1.0a1/tests/support/voice.py +230 -0
- shoal-0.1.0a1/tests/tasks/__init__.py +0 -0
- shoal-0.1.0a1/tests/tasks/cron.py +86 -0
- shoal-0.1.0a1/tests/tasks/jobs.py +39 -0
- shoal-0.1.0a1/tests/tasks/loop.py +222 -0
- shoal-0.1.0a1/tests/tasks/scheduler.py +350 -0
- shoal-0.1.0a1/tests/ui/__init__.py +1 -0
- shoal-0.1.0a1/tests/ui/action_row.py +141 -0
- shoal-0.1.0a1/tests/ui/button.py +76 -0
- shoal-0.1.0a1/tests/ui/content.py +86 -0
- shoal-0.1.0a1/tests/ui/inputs.py +103 -0
- shoal-0.1.0a1/tests/ui/item.py +26 -0
- shoal-0.1.0a1/tests/ui/label.py +50 -0
- shoal-0.1.0a1/tests/ui/layout.py +101 -0
- shoal-0.1.0a1/tests/ui/lifetime.py +87 -0
- shoal-0.1.0a1/tests/ui/modal.py +306 -0
- shoal-0.1.0a1/tests/ui/select.py +156 -0
- shoal-0.1.0a1/tests/ui/text_input.py +41 -0
- shoal-0.1.0a1/tests/ui/view.py +359 -0
- shoal-0.1.0a1/tests/voice/__init__.py +0 -0
- shoal-0.1.0a1/tests/voice/audio.py +204 -0
- shoal-0.1.0a1/tests/voice/connection.py +413 -0
- shoal-0.1.0a1/tests/voice/crypto.py +69 -0
- shoal-0.1.0a1/tests/voice/dave.py +127 -0
- shoal-0.1.0a1/tests/voice/receive/__init__.py +0 -0
- shoal-0.1.0a1/tests/voice/receive/opus.py +84 -0
- shoal-0.1.0a1/tests/voice/receive/stream.py +159 -0
- shoal-0.1.0a1/tests/voice/rtp.py +49 -0
- shoal-0.1.0a1/tests/voice/udp.py +108 -0
shoal-0.1.0a1/.gitignore
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Local assistant instructions stay local
|
|
2
|
+
CLAUDE.md
|
|
3
|
+
.claude/
|
|
4
|
+
|
|
5
|
+
# Python
|
|
6
|
+
__pycache__/
|
|
7
|
+
*.py[cod]
|
|
8
|
+
.venv/
|
|
9
|
+
build/
|
|
10
|
+
dist/
|
|
11
|
+
*.egg-info/
|
|
12
|
+
|
|
13
|
+
# Tool caches and reports
|
|
14
|
+
.coverage
|
|
15
|
+
.coverage.*
|
|
16
|
+
coverage.xml
|
|
17
|
+
htmlcov/
|
|
18
|
+
.mypy_cache/
|
|
19
|
+
.pytest_cache/
|
|
20
|
+
.ruff_cache/
|
|
21
|
+
.hypothesis/
|
|
22
|
+
.import_linter_cache/
|
|
23
|
+
|
|
24
|
+
# Docs build
|
|
25
|
+
site/
|
|
26
|
+
|
|
27
|
+
# Secrets
|
|
28
|
+
.env
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes are recorded here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
|
|
4
|
+
and the project uses [Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
## [0.1.0a1] - 2026-10-29
|
|
9
|
+
|
|
10
|
+
The first pre-release.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Releases: pushing a `vX.Y.Z` tag publishes to PyPI through trusted publishing and creates the
|
|
15
|
+
GitHub release from the changelog (`.github/workflows/release.yml`).
|
|
16
|
+
- Content scanning flags (Discord, October 28, 2026): `AttachmentFlags` and `EmbedFlags` gain
|
|
17
|
+
`contains_explicit_content`, `contains_gore_content` and `contains_self_harm_content`, and
|
|
18
|
+
`UnfurledMediaItem.content_scan_metadata` carries `ContentScanMetadataFlags`.
|
|
19
|
+
- `shoal.tasks`: `tasks.loop` (intervals, times of day, hooks), cron expressions with time zones, and
|
|
20
|
+
`client.scheduler` for jobs at a time, after a delay or on a cron schedule, kept in memory or in
|
|
21
|
+
Redis (`RedisJobStore`: survives restarts, each due job claimed by one process, leased).
|
|
22
|
+
- Voice receive decoding: `VoiceClient.audio()` yields PCM per user, reordered, with losses
|
|
23
|
+
concealed, through a small libopus binding (`shoal.voice.receive`); packets from a sender Discord
|
|
24
|
+
has not named yet are held until it does instead of dropped.
|
|
25
|
+
- Per-field cache trimming: `CachePolicy(fields=...)` stores only the named fields of a kind.
|
|
26
|
+
- `shoal.core`: `Snowflake`, immutable `BaseFlags` bitfields, open enums that accept values Discord adds
|
|
27
|
+
later, the msgspec JSON and msgpack codec, the `Clock` protocol with an interruptible sleep, a shared
|
|
28
|
+
backoff function, owner-only atomic file writes, and the metrics catalog with the `Metrics` protocol.
|
|
29
|
+
- `shoal.payloads`: typed payloads, one module per Discord domain (API constants, users, permissions,
|
|
30
|
+
emoji, guilds, channels, messages, voice, gateway, gateway events, rate limits, errors), each linked to
|
|
31
|
+
its section of Discord's docs. Includes message search parameters and results, presences, voice states, reactions and
|
|
32
|
+
thread events, and handling for obfuscated channels (`Channel.is_obfuscated`, `Channel.visible_name`).
|
|
33
|
+
- `shoal.rest`: `RestClient` with bot and bearer tokens, typed routes with docs links, per-bucket and
|
|
34
|
+
global rate limiting, the invalid request guard, stop-after-401, and Search Guild Messages with
|
|
35
|
+
retries while Discord builds the index.
|
|
36
|
+
- Every HTTP endpoint Discord documents (237), grouped by resource with short names:
|
|
37
|
+
`rest.messages.create`, `rest.members.ban`, `rest.commands.overwrite_global`. The coverage page
|
|
38
|
+
lists the Shoal method for each Discord endpoint. Resources: `applications`, `audit_logs`,
|
|
39
|
+
`automod`, `channels`, `commands`, `emojis`, `entitlements`, `gateway`, `guilds` (prune, widget,
|
|
40
|
+
onboarding, welcome screen and the guild's other settings), `identities`, `interactions`, `invites`,
|
|
41
|
+
`lobbies`, `members` (bans
|
|
42
|
+
included), `messages`, `oauth2`, `polls`, `reactions`, `role_connections`, `roles`,
|
|
43
|
+
`scheduled_events`, `skus`, `soundboard`, `stage_instances`, `stickers`, `subscriptions`,
|
|
44
|
+
`templates`, `threads`, `users`, `voice`, `voice_states` and `webhooks`, each with its payloads. The
|
|
45
|
+
four endpoints Discord has deprecated or disabled are left out.
|
|
46
|
+
- Every gateway event Discord documents (82) is typed: `DISPATCH_PAYLOADS` maps each dispatch name
|
|
47
|
+
to the type its data decodes into, and the coverage page lists them. Includes Auto Moderation,
|
|
48
|
+
audit log entries, bans, stickers, integrations, invites, scheduled event subscribers, soundboard
|
|
49
|
+
sounds, polls, typing, voice channel effects and status, voice server updates and `RATE_LIMITED`
|
|
50
|
+
(which the shard logs). Shards send the new Request Soundboard Sounds and Request Channel Info
|
|
51
|
+
commands; `ShardManager.request_soundboard_sounds` splits a guild list across shards.
|
|
52
|
+
- The cache holds stickers, scheduled events, stage instances and soundboard sounds, follows poll
|
|
53
|
+
votes, scheduled event subscriber counts and the bot's thread membership, and can be read
|
|
54
|
+
synchronously with `CacheStore.peek` and `peek_scoped` (snapshot format 3; older snapshots are
|
|
55
|
+
ignored and the cache starts cold once).
|
|
56
|
+
- File uploads: `File` (bytes, or read off the event loop with `File.from_path`) on every endpoint
|
|
57
|
+
that takes files. Message-like requests describe new files in `attachments` themselves; edits only
|
|
58
|
+
append, so existing attachments are never dropped by accident. `image_data` and `sound_data` build
|
|
59
|
+
the data URIs Discord's image and sound fields take.
|
|
60
|
+
- Payloads that hold a credential hide it in their `repr` (`REDACTED_FIELDS`), and invite and
|
|
61
|
+
template codes join tokens among the path values kept out of logs.
|
|
62
|
+
- `shoal.gateway`: zlib-stream and zstd-stream decompression, shard routing, `max_concurrency` identify
|
|
63
|
+
scheduling, persistent sessions, heartbeat tracking, the send limiter, event delay tracking, a
|
|
64
|
+
resumable `Shard` that falls back from a failing resume URL to the main gateway, and `ShardManager`
|
|
65
|
+
with cluster assignment (`cluster_shard_ids`).
|
|
66
|
+
- `shoal.cache`: per-entity cache policies, per-shard freshness tracking, scope-indexed collections,
|
|
67
|
+
snapshots that line up with resumed sessions, restart strategies, and a `CacheUpdater` that applies
|
|
68
|
+
every cache-relevant gateway event (guilds, channels, threads, roles, emojis, members, users,
|
|
69
|
+
messages, reactions, presences and voice states).
|
|
70
|
+
- `shoal.ipc`: `EventServer` and `EventClient` to stream gateway events between processes over TCP, with
|
|
71
|
+
token authentication, optional TLS, event filtering and reconnects.
|
|
72
|
+
- Application commands: typed commands, options, choices and permissions, and the 15 live command
|
|
73
|
+
endpoints. Creating or editing one guild command takes its own type, without the global-only
|
|
74
|
+
fields.
|
|
75
|
+
- Components: every message and modal component, including Components V2 and the newer modal inputs,
|
|
76
|
+
with layout unions that make invalid nesting a type error. Received components decode lazily
|
|
77
|
+
(`parse_components`), so a component type newer than Shoal never breaks a message.
|
|
78
|
+
- Interactions: one class per interaction type and per response type, `parse_interaction` with an
|
|
79
|
+
`UnknownInteraction` fallback, modal submissions, resolved data, and the eight response and
|
|
80
|
+
follow-up endpoints. Interaction tokens never appear in logs, errors or metrics.
|
|
81
|
+
- `shoal.endpoint`: receive interactions over HTTP without a gateway, with Ed25519
|
|
82
|
+
signature checks, inline or callback answers, and handler timing against the 3-second deadline
|
|
83
|
+
(`pip install 'shoal[interactions]'`). `client.endpoint(public_key)` serves a client's views,
|
|
84
|
+
modals and commands over HTTP; the first response becomes the HTTP reply.
|
|
85
|
+
- `shoal.models`: guilds, members, roles, channels, threads, messages, users, emojis and embeds, with
|
|
86
|
+
synchronous lookups that read the cache (`guild.get_member`, `get_member_named`) and actions that
|
|
87
|
+
call REST (`send`, `edit`, `ban`, `add_roles`, `history`, `typing`).
|
|
88
|
+
- `shoal.client`: `Client` with login, sharded connect and `run`; events as models with the earlier
|
|
89
|
+
state read from the cache; raw events that are never dropped; readiness that does not wait for
|
|
90
|
+
member chunking; `wait_for`; interactions with `response` and `followup`.
|
|
91
|
+
- `shoal.ui`: `View`, `Modal`, `Button`, the five selects and `TextInput`, with persistent views,
|
|
92
|
+
timeouts and interaction checks.
|
|
93
|
+
- `shoal.app_commands`: `CommandTree`, slash commands, groups and context menus with options from type
|
|
94
|
+
hints, autocomplete, checks, cooldowns, error handlers and sync; Discord's limits are checked where
|
|
95
|
+
the decorator is written.
|
|
96
|
+
- `shoal.emojis`: the application's emojis by name (`client.app_emojis.approve`), loaded at login,
|
|
97
|
+
with declared names and unicode fallbacks, and a sync from a folder of images (in the bot, or
|
|
98
|
+
`python -m shoal.emojis sync`) that skips unchanged images and never re-uploads what it lost track of.
|
|
99
|
+
- `shoal.commands`: prefix and hybrid commands. `Bot` (a `Client` with commands and a slash command
|
|
100
|
+
tree), `Context`, commands and groups with argument conversion from type hints (`Greedy`, `*words`,
|
|
101
|
+
the rest of the message, converters), checks, cooldowns and `max_concurrency`, cogs with listeners,
|
|
102
|
+
extensions that load, unload and reload (taking their slash commands with them), a help command,
|
|
103
|
+
and hybrid commands and groups used both as `!name` and `/name`, with their checks run on both
|
|
104
|
+
sides before any option is converted.
|
|
105
|
+
- `shoal.core.cooldown`: one sliding-window cooldown for both command frameworks, which can be asked
|
|
106
|
+
how long to wait without recording a use (`get_retry_after`).
|
|
107
|
+
- `Client` options for large bots: `metrics`, `session_store`, `cache_snapshot` (loaded before
|
|
108
|
+
connecting, saved after the shards close), `identify_limiter`, `clock`, and the shards' connection
|
|
109
|
+
settings (`compression`, `large_threshold`, `capabilities`, ...). `commands.Bot` takes them too.
|
|
110
|
+
- The most used names at the top level (`shoal.Client`, `shoal.Embed`, `shoal.app_commands`), loaded
|
|
111
|
+
on first use, so importing one layer still loads nothing else.
|
|
112
|
+
- `guild.chunk()` and `guild.query_members()`: members over the gateway. Concurrent `chunk()` calls
|
|
113
|
+
share one request, the cache drops members who are not in the full list, and a request Discord rate
|
|
114
|
+
limits fails at once (`MemberRequestRateLimitedError`).
|
|
115
|
+
- `chunk_guilds_at_startup`: with the Server Members intent, every guild that arrives without its
|
|
116
|
+
member list is chunked as it streams in, one guild at a time per shard, retrying after
|
|
117
|
+
`RATE_LIMITED`; each shard is ready once its startup guilds are chunked, and joined guilds are
|
|
118
|
+
chunked too. On by default with that intent; pass `False` to chunk on demand.
|
|
119
|
+
- `docs/examples/`: bots to copy and run, from a one-file ping bot to a big bot with Prometheus and
|
|
120
|
+
Grafana, each tested against Discord.
|
|
121
|
+
- `shoal_events_dropped_total`: gateway payloads and dispatches dropped because they did not decode.
|
|
122
|
+
- Webhook events: every event Discord posts to an app's Webhook Events URL is typed
|
|
123
|
+
(`WebhookEventPayload`, `WEBHOOK_EVENT_PAYLOADS`), and `Endpoint(..., events=handler)` receives them,
|
|
124
|
+
acknowledging each at once. `client.endpoint()` emits them as events (`on_application_authorized`,
|
|
125
|
+
`on_entitlement_create`, `on_lobby_message_create`, ...). `shoal_http_webhook_events_total` counts
|
|
126
|
+
them.
|
|
127
|
+
- Components V2 in `shoal.ui`: `LayoutView` (the whole message), `ActionRow` (with `@row.button`
|
|
128
|
+
and `@row.select`), `Container`, `Section`, `TextDisplay`, `Thumbnail`, `MediaGallery`, `File` and
|
|
129
|
+
`Separator`; items declared on a view or container class are copied per instance. Modals take
|
|
130
|
+
`Label`s around any input, `TextDisplay`s, and the new `FileUpload`, `RadioGroup`, `CheckboxGroup`
|
|
131
|
+
and `Checkbox`. Every documented limit is checked before sending, and a Components V2 message sent
|
|
132
|
+
with content, embeds, a poll or stickers raises `TypeError`.
|
|
133
|
+
- `File.uri`: the `attachment://` reference embeds and components use for a file sent with them
|
|
134
|
+
(with the `SPOILER_` prefix a spoiler file is uploaded under).
|
|
135
|
+
- `pip install "shoal[all]"` installs every optional dependency.
|
|
136
|
+
- `shoal.compat`: familiar names for migrating bots (`import shoal.compat as discord`): the error
|
|
137
|
+
names without `Error` (`Forbidden`, `commands.MissingRequiredArgument`), renamed types under their
|
|
138
|
+
old names, `compat.utils` (`get`, `find`, `oauth_url`, `snowflake_time`, `as_chunks`, ...).
|
|
139
|
+
- Prefix commands: `FlagConverter` and `flag()` (also as hybrid options), `param()` with context
|
|
140
|
+
defaults (`Author`, `CurrentChannel`, `CurrentGuild`) and descriptions, `clean_content`, a converter
|
|
141
|
+
class for every conversion and new ones for colours, invites, stickers, scheduled events, soundboard
|
|
142
|
+
sounds and timestamps, `Attachment` parameters, `run_converters`, `dynamic_cooldown`,
|
|
143
|
+
`bot_has_any_role`, `GroupCog`, `MinimalHelpCommand`, `Paginator` and `AutoShardedBot`.
|
|
144
|
+
- Slash commands: `Transformer` and `Transform` (run after the checks), `Translator` and `locale_str`
|
|
145
|
+
(localized names and descriptions on `sync`), `dynamic_cooldown`, and `guild_install`,
|
|
146
|
+
`user_install`, `dm_only` and `private_channel_only`. New errors: `CommandAlreadyRegisteredError`,
|
|
147
|
+
`CommandLimitReachedError`, `CommandSignatureMismatchError`, `CommandSyncFailureError` and
|
|
148
|
+
`MissingApplicationIDError`, each a subclass of what was raised before.
|
|
149
|
+
- `shoal.core.formatting`: `escape_markdown`, `remove_markdown`, `escape_mentions`, `format_dt` and the
|
|
150
|
+
mention and timestamp patterns; `Message.clean_content`.
|
|
151
|
+
- `PrivilegedIntentsRequiredError`: the gateway's close for intents the application has not enabled,
|
|
152
|
+
saying where to enable them.
|
|
153
|
+
- `interaction.namespace` is typed (`OptionNamespace`), so `interaction.namespace.member` type-checks.
|
|
154
|
+
- `shoal.redis` (`shoal[redis]`): state shared by a bot's processes. `RedisIdentifyLimiter` and
|
|
155
|
+
`RedisSessionStore` for clusters, `RedisRateLimiter` (route buckets, the global limit and the
|
|
156
|
+
invalid request window counted across processes) and `RedisCollections` for a cache in Redis.
|
|
157
|
+
`CacheStore(collections=...)` takes any `CollectionFactory`.
|
|
158
|
+
- Events through a Redis stream (`RedisEventStream`, `RedisEventConsumer`): a broker for IPC, with
|
|
159
|
+
consumer groups that share the events and read again what a crashed worker left unfinished.
|
|
160
|
+
- IPC over Unix sockets: `EventServer(path=...)` and `EventClient.unix(path, ...)`.
|
|
161
|
+
- Cluster coordination: a `ShardDirectory` where every `ShardManager` reports its shards, and a
|
|
162
|
+
`CommandRouter` that sends a gateway command on the shard of its guild whichever cluster runs it
|
|
163
|
+
(`ShardManager.send_command`, `send_to_guild`); in memory and in Redis (`RedisShardDirectory`,
|
|
164
|
+
`RedisCommandRouter`). `Client` takes them as `shard_directory=` and `command_router=`, and
|
|
165
|
+
`Client.shard_manager` reaches the shards.
|
|
166
|
+
- Voice messages: `send(voice_message=VoiceMessage.from_ogg(...))` on channels, replies, contexts,
|
|
167
|
+
interaction responses and follow-ups, with the duration read from the Ogg Opus stream and
|
|
168
|
+
`waveform_of` for the preview; `File` carries `duration` and `waveform` into the attachment.
|
|
169
|
+
- Voice (`shoal[voice]`): `await channel.connect()` joins a voice channel and returns the guild's
|
|
170
|
+
`VoiceClient`, which plays (`OggOpusSource`, `FFmpegSource`), pauses, moves and listens
|
|
171
|
+
(`listen()`), and joins again by itself when the session is lost. Calls are end-to-end encrypted
|
|
172
|
+
with DAVE (through `davey`); packets are sealed with AES-256-GCM or XChaCha20-Poly1305. The voice
|
|
173
|
+
gateway (v8, buffered resume), IP discovery and the payloads are in `shoal.voice` and
|
|
174
|
+
`shoal.payloads.voice_gateway`; `shoal.core.ogg` reads and writes Ogg Opus.
|
|
175
|
+
- Resharding without downtime: `ShardManager.reshard` and `Client.reshard` start the new shard count
|
|
176
|
+
beside the old one and switch in every cluster once all of it is ready.
|
|
177
|
+
- `RestProxy`: one process holds a bot's token and rate limits, and every other process sends REST
|
|
178
|
+
through it with only the proxy's secret (`RestClient(None, base_url=..., proxy_secret=...)`).
|
|
179
|
+
`RouteTable` finds the documented route of a method and path; `rate_limit_of` reads a response's
|
|
180
|
+
rate limits.
|
|
181
|
+
- `GatewayRequiredError` and `MemberRequestRateLimitedError` are exported from `shoal.models`.
|
|
182
|
+
- `CollectionFactory.shared` and `CacheStore.resharded()`: a store over shared collections numbers
|
|
183
|
+
its writes on from the clock and keeps no snapshots; after a reshard, entries keep their freshness.
|
|
184
|
+
- `shoal.metrics.prometheus`: `PrometheusMetrics`, exporting every metric in the catalog.
|
|
185
|
+
- `shoal.diagnostics`: `LoopMonitor`, which names the code blocking the event loop while it blocks.
|
|
186
|
+
- Documentation site with generated API reference, guides (including writing a bot), and a Discord
|
|
187
|
+
API coverage page checked weekly against Discord's docs.
|
|
188
|
+
|
|
189
|
+
### Changed
|
|
190
|
+
|
|
191
|
+
- `SessionStore.delete` takes the shard count too, and sessions are kept per shard count
|
|
192
|
+
(`FileSessionStore` writes `shard-<count>-<id>.json`), so a reshard's two shard counts never
|
|
193
|
+
overwrite each other's sessions. Session files from earlier versions are not read: each shard
|
|
194
|
+
identifies once after upgrading.
|
|
195
|
+
- A shard builds its identify once it holds its identify slot, so a presence set while it waited
|
|
196
|
+
goes out with it.
|
|
197
|
+
- `Context.reply` takes every keyword `Context.send` does.
|
|
198
|
+
|
|
199
|
+
- `ui.Item` is the base of every component; the custom ID, `row` and callbacks moved to
|
|
200
|
+
`ui.InteractiveItem`. `TextInput.to_component()` is the bare input (a modal puts it in a label),
|
|
201
|
+
and its `label` is optional inside a `ui.Label`.
|
|
202
|
+
- A slash command missing a required option, or sent one it does not have, raises
|
|
203
|
+
`CommandSignatureMismatchError` (sync the tree) instead of `TransformerError`.
|
|
204
|
+
- `to_partial_emoji` raises `PartialEmojiConversionFailureError` and `to_object`
|
|
205
|
+
`ObjectNotFoundError`; both are still `BadArgumentError`s.
|
|
206
|
+
- Renamed for the naming rules: `rest.channels.set_voice_status` is `edit_voice_status`
|
|
207
|
+
(`EditVoiceStatusParams`), `rest.guilds.edit_widget` is `edit_widget_settings`
|
|
208
|
+
(`EditWidgetSettingsParams`), `BulkLobbyMemberParams` is `BulkEditLobbyMemberParams` and
|
|
209
|
+
`EditRoleConnectionParams` is `EditCurrentRoleConnectionParams`. `Cooldown.retry_after` is
|
|
210
|
+
`update_rate_limit`. `GATEWAY_DOMAIN` moved to `shoal.gateway.defaults`.
|
|
211
|
+
- `~flags` inverts all 64 bits, so `permissions & ~other` keeps bits Discord added that Shoal does not
|
|
212
|
+
know yet; it used to clear them.
|
|
213
|
+
- A view sent with `response.send_message` answers only on the message it was sent with, and views and
|
|
214
|
+
modals are forgotten as soon as they stop. A user has at most five modals waiting; opening another
|
|
215
|
+
stops the oldest.
|
|
216
|
+
- An interaction counts as answered only once Discord accepted the response, so a failed response can
|
|
217
|
+
be sent again. Over HTTP, code after the response runs once the reply is written.
|
|
218
|
+
- Channels from `GUILD_CREATE` get their guild's ID when Discord leaves it out.
|
|
219
|
+
- Offline presences are not cached (an absent presence reads as offline).
|
|
220
|
+
- A close code that ends the session backs off before identifying again, as other failures do.
|
|
221
|
+
- `edit(attachments=...)` on messages is the list of attachments to keep, as existing `Attachment`s
|
|
222
|
+
and new `File`s; attachments left out are removed. Leaving it out still keeps them all.
|
|
223
|
+
- `max_rate_limit_wait` also bounds the wait before sending (an empty bucket, a global pause, the
|
|
224
|
+
invalid request guard): a longer wait raises `RateLimitedError` at once instead of sleeping.
|
|
225
|
+
- A 429 whose JSON body says `global` pauses every route, even without the `X-RateLimit-Global`
|
|
226
|
+
header.
|
|
227
|
+
- `Guild.get_user` is removed; use `Client.get_user`. `shoal.models.channel.resolve_guild_channel`
|
|
228
|
+
is `resolve_cached_channel` (it also returns DM channels).
|
|
229
|
+
- `Client.connect()` takes the token `login` used when none is given.
|
|
230
|
+
- Cache snapshots record the shard count; `load_snapshot(shard_count=...)` starts cold after
|
|
231
|
+
resharding. `Client(cache_snapshot=...)` passes it.
|
|
232
|
+
- `Identify`, `Resume` and the IPC `EventFrame` keep the token out of their repr, and `User` keeps its
|
|
233
|
+
email out of it.
|
|
234
|
+
- The IPC client resets its reconnect backoff only after a subscription lasted a minute, so a worker
|
|
235
|
+
dropped as too slow over and over backs off.
|
|
236
|
+
|
|
237
|
+
### Fixed
|
|
238
|
+
|
|
239
|
+
- A cooldown's `retry_after` could round a hair above its window (60.00000000000003 for 60 s).
|
|
240
|
+
- `EventServer.close()` no longer hangs when the event loop hands over a connection after the
|
|
241
|
+
others were disconnected (seen on macOS).
|
shoal-0.1.0a1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shoal 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.
|
shoal-0.1.0a1/PKG-INFO
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: shoal
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: A fully typed Discord API library for Python, built to scale from one guild to millions.
|
|
5
|
+
Project-URL: Documentation, https://shoal.readthedocs.io/
|
|
6
|
+
Project-URL: Repository, https://github.com/relateful/shoal
|
|
7
|
+
Project-URL: Issues, https://github.com/relateful/shoal/issues
|
|
8
|
+
Author: Shoal contributors
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: asyncio,bot,discord,discord-api,gateway,sharding
|
|
12
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
13
|
+
Classifier: Framework :: AsyncIO
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Communications :: Chat
|
|
22
|
+
Classifier: Topic :: Internet
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.12
|
|
25
|
+
Requires-Dist: aiohttp>=3.13
|
|
26
|
+
Requires-Dist: msgspec>=0.20
|
|
27
|
+
Provides-Extra: all
|
|
28
|
+
Requires-Dist: cryptography>=50.0.2; extra == 'all'
|
|
29
|
+
Requires-Dist: davey>=0.1.7; extra == 'all'
|
|
30
|
+
Requires-Dist: prometheus-client>=0.21; extra == 'all'
|
|
31
|
+
Requires-Dist: redis>=5.2; extra == 'all'
|
|
32
|
+
Requires-Dist: zstandard>=0.23; (python_version < '3.14') and extra == 'all'
|
|
33
|
+
Provides-Extra: interactions
|
|
34
|
+
Requires-Dist: cryptography>=50.0.2; extra == 'interactions'
|
|
35
|
+
Provides-Extra: prometheus
|
|
36
|
+
Requires-Dist: prometheus-client>=0.21; extra == 'prometheus'
|
|
37
|
+
Provides-Extra: redis
|
|
38
|
+
Requires-Dist: redis>=5.2; extra == 'redis'
|
|
39
|
+
Provides-Extra: voice
|
|
40
|
+
Requires-Dist: cryptography>=50.0.2; extra == 'voice'
|
|
41
|
+
Requires-Dist: davey>=0.1.7; extra == 'voice'
|
|
42
|
+
Provides-Extra: zstd
|
|
43
|
+
Requires-Dist: zstandard>=0.23; (python_version < '3.14') and extra == 'zstd'
|
|
44
|
+
Description-Content-Type: text/markdown
|
|
45
|
+
|
|
46
|
+
# Shoal
|
|
47
|
+
|
|
48
|
+
A fully typed Discord API library for Python, built for bots that grow. It keeps the names you know
|
|
49
|
+
from discord.py (`commands.Bot`, `interaction.response.send_message`, `ui.View`, `tree.sync()`) and
|
|
50
|
+
fixes what makes discord.py painful once a bot gets big.
|
|
51
|
+
|
|
52
|
+
> **Status: pre-release.** Everything below is built and tested, but names can still change before
|
|
53
|
+
> 1.0. Install the pre-release with `pip install --pre shoal`. See the [roadmap](https://github.com/relateful/shoal/blob/main/docs/roadmap.md).
|
|
54
|
+
|
|
55
|
+
## Why not discord.py?
|
|
56
|
+
|
|
57
|
+
discord.py is a good library for a bot in a few servers. Shoal is for bots that keep growing, and it
|
|
58
|
+
is just as easy for the small ones.
|
|
59
|
+
|
|
60
|
+
### It starts fast, however many shards you run
|
|
61
|
+
|
|
62
|
+
discord.py identifies its shards one after another, about five seconds apart, and ignores the
|
|
63
|
+
`max_concurrency` Discord gives large bots. Shoal identifies `max_concurrency` shards at a time, as
|
|
64
|
+
Discord documents. At 512 shards with a concurrency of 16, that is under three minutes instead of
|
|
65
|
+
about 43.
|
|
66
|
+
|
|
67
|
+
### Restarts and deploys don't cost you the gateway
|
|
68
|
+
|
|
69
|
+
- **Sessions survive restarts.** discord.py keeps the session only in memory, so every restart
|
|
70
|
+
identifies every shard again, and Discord sends every guild again. Shoal saves sessions (to files,
|
|
71
|
+
or Redis) and resumes them on the next start.
|
|
72
|
+
- **The cache survives restarts.** Shoal restores the cache from a snapshot, marked stale until
|
|
73
|
+
Discord confirms each entry, so the bot answers from a warm cache from the first second.
|
|
74
|
+
- **A failed resume still resumes.** discord.py falls back to the main gateway only when the resume
|
|
75
|
+
URL answers with a 5xx, and then starts a new session. Shoal falls back on any failure (a timeout,
|
|
76
|
+
a 5xx, an early close) and resumes there, as Discord recommends.
|
|
77
|
+
- **Change shard counts without downtime.** `bot.reshard(new_count)` connects the new shards beside
|
|
78
|
+
the running ones and switches over once they are all ready. discord.py has no way to do this short
|
|
79
|
+
of a restart.
|
|
80
|
+
|
|
81
|
+
### More than one process is built in
|
|
82
|
+
|
|
83
|
+
discord.py leaves IPC to you. Shoal includes it:
|
|
84
|
+
|
|
85
|
+
- **Gateway and workers.** One process holds the shards and streams events (`shoal.ipc`, over TCP
|
|
86
|
+
or a Unix socket, or through a Redis stream). Workers run your commands and can restart as often
|
|
87
|
+
as you deploy.
|
|
88
|
+
- **Clusters.** Several processes or hosts run one bot through Redis. They share one identify
|
|
89
|
+
limiter, the REST rate limits, sessions, a directory of every shard, a command router (join a
|
|
90
|
+
voice channel from any cluster) and the cache.
|
|
91
|
+
- **A REST proxy.** One process holds the token and the rate limits for all the others.
|
|
92
|
+
|
|
93
|
+
### It protects the bot from bans
|
|
94
|
+
|
|
95
|
+
- Discord bans an IP for 10,000 invalid requests in 10 minutes. discord.py does not count them.
|
|
96
|
+
Shoal counts the 401, 403 and 429 responses Discord counts, and holds requests back before the
|
|
97
|
+
limit.
|
|
98
|
+
- After a 401 (a revoked token), discord.py keeps sending requests that fail. Shoal stops at the
|
|
99
|
+
first one.
|
|
100
|
+
|
|
101
|
+
### You can see what it is doing
|
|
102
|
+
|
|
103
|
+
- **Prometheus metrics** for gateway events and latency per shard, REST requests and rate limits by
|
|
104
|
+
route, the cache, IPC, voice and event loop health. discord.py has none.
|
|
105
|
+
- **A blocked event loop is caught as it happens.** discord.py warns once the heartbeat has been
|
|
106
|
+
stuck for 10 seconds, and again every 10 seconds after. Shoal's watchdog
|
|
107
|
+
(`async with LoopMonitor():`) reports blocks over 0.25 seconds (configurable), names the line of
|
|
108
|
+
your code that is blocking while it blocks, logs how long it lasted, and records it in metrics.
|
|
109
|
+
- **Late events are noticed.** Shoal measures how long each message and interaction took to arrive,
|
|
110
|
+
and warns when they arrive more than five seconds late: the sign that events queue up before your
|
|
111
|
+
code sees them.
|
|
112
|
+
|
|
113
|
+
### Typed, and true to Discord's docs
|
|
114
|
+
|
|
115
|
+
- Strict pyright and mypy over the library and its tests, with `Any` banned.
|
|
116
|
+
- Every type and endpoint links to its section of Discord's docs, and CI checks the links. Every
|
|
117
|
+
HTTP endpoint Discord documents and every gateway event is covered, as the
|
|
118
|
+
[coverage page](https://github.com/relateful/shoal/blob/main/docs/coverage.md), generated from Discord's docs, shows.
|
|
119
|
+
- A field that is absent and a field that is `null` stay different (`UNSET` and `None`), so an edit
|
|
120
|
+
can tell "leave it" from "clear it". discord.py turns most of them into `None`.
|
|
121
|
+
- When Discord adds an enum value, Shoal decodes it as a real member of the enum, with its value
|
|
122
|
+
kept. discord.py makes an `unknown_<value>` stand-in that is not a member.
|
|
123
|
+
- REST is public and standalone: `RestClient` covers every endpoint as `rest.<resource>.<verb>`, with
|
|
124
|
+
bot or OAuth2 bearer tokens, for workers and dashboards. discord.py's `Client.http` is internal and
|
|
125
|
+
undocumented.
|
|
126
|
+
|
|
127
|
+
### And what a bot needs today
|
|
128
|
+
|
|
129
|
+
- Slash, prefix and hybrid commands, cogs and extensions, views, modals and Components V2 layouts.
|
|
130
|
+
- Interactions over the gateway, or over HTTP with no gateway at all (signed, verified endpoint).
|
|
131
|
+
discord.py only has the gateway.
|
|
132
|
+
- Voice: play and **listen**, end-to-end encrypted (DAVE), as Opus packets or decoded PCM per user
|
|
133
|
+
(decoding needs libopus). discord.py can play, but receiving audio takes a third-party extension.
|
|
134
|
+
- Voice messages, polls, the application's own emojis synced from a folder, and member chunking as
|
|
135
|
+
guilds arrive (`chunk_guilds_at_startup`).
|
|
136
|
+
- A cache with a policy per entity (size, TTL, whether to snapshot, which fields to keep), instead
|
|
137
|
+
of one fixed cache.
|
|
138
|
+
- Loops (`tasks.loop`) and a scheduler for reminders and cron jobs that survive restarts with
|
|
139
|
+
Redis, without a sleeping task per job.
|
|
140
|
+
|
|
141
|
+
> **Coming from discord.py?** `import shoal.compat as discord` gives you the old names, so a bot can move
|
|
142
|
+
> file by file. See the [compat example](https://github.com/relateful/shoal/tree/main/docs/examples/compat/) and the
|
|
143
|
+
> [migration guide](https://github.com/relateful/shoal/blob/main/docs/guide/migrating-from-discord-py.md).
|
|
144
|
+
|
|
145
|
+
## Install
|
|
146
|
+
|
|
147
|
+
Shoal needs Python 3.12 or newer. While it is in pre-release, pip needs `--pre`:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
pip install --pre shoal # the core
|
|
151
|
+
pip install --pre "shoal[voice]" # join voice channels, play and listen
|
|
152
|
+
pip install --pre "shoal[redis]" # share limits, sessions, cache, events and shards across processes
|
|
153
|
+
pip install --pre "shoal[prometheus]" # Prometheus metrics
|
|
154
|
+
pip install --pre "shoal[interactions]" # receive interactions over HTTP, without a gateway
|
|
155
|
+
pip install --pre "shoal[zstd]" # zstd gateway compression on Python 3.12 and 3.13
|
|
156
|
+
pip install --pre "shoal[all]" # everything above
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
To work on Shoal itself, install it from a clone with `pip install -e ".[all]"`.
|
|
160
|
+
|
|
161
|
+
Each extra adds a dependency only some bots need, so the core install stays small.
|
|
162
|
+
|
|
163
|
+
## A taste
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
import os
|
|
167
|
+
|
|
168
|
+
import shoal
|
|
169
|
+
from shoal import commands
|
|
170
|
+
|
|
171
|
+
bot = commands.Bot("!", intents=shoal.Intents.default().with_flags(message_content=True))
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@bot.hybrid_command(description="Say hello")
|
|
175
|
+
async def hello(ctx: commands.Context, who: shoal.Member | None = None) -> None:
|
|
176
|
+
await ctx.send(f"Hello {(who or ctx.author).mention}!")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
bot.run(os.environ["DISCORD_TOKEN"])
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`!hello` and `/hello` both work (sync the slash command once with `await bot.tree.sync()`).
|
|
183
|
+
|
|
184
|
+
## Examples
|
|
185
|
+
|
|
186
|
+
[29 runnable bots](https://github.com/relateful/shoal/tree/main/docs/examples/), each with its own README and each run against Discord, from
|
|
187
|
+
the [smallest bot](https://github.com/relateful/shoal/tree/main/docs/examples/hello/) to [role menus](https://github.com/relateful/shoal/tree/main/docs/examples/roles/buttons/),
|
|
188
|
+
[tickets](https://github.com/relateful/shoal/tree/main/docs/examples/tickets/), [music](https://github.com/relateful/shoal/tree/main/docs/examples/voice/music/) and
|
|
189
|
+
[a big bot](https://github.com/relateful/shoal/tree/main/docs/examples/bigbot/) with Prometheus, Grafana, warm restarts and resharding, plus
|
|
190
|
+
[clusters](https://github.com/relateful/shoal/tree/main/docs/examples/clusters/) and [workers](https://github.com/relateful/shoal/tree/main/docs/examples/workers/).
|
|
191
|
+
|
|
192
|
+
## Documentation
|
|
193
|
+
|
|
194
|
+
- Guides: [writing a bot](https://github.com/relateful/shoal/blob/main/docs/guide/bot.md), [commands and interactions](https://github.com/relateful/shoal/blob/main/docs/guide/interactions.md),
|
|
195
|
+
[caching and restarts](https://github.com/relateful/shoal/blob/main/docs/guide/caching.md), [scaling, sharding and IPC](https://github.com/relateful/shoal/blob/main/docs/guide/scaling.md),
|
|
196
|
+
[voice](https://github.com/relateful/shoal/blob/main/docs/guide/voice.md), [loops and scheduled jobs](https://github.com/relateful/shoal/blob/main/docs/guide/tasks.md),
|
|
197
|
+
[dashboards and REST](https://github.com/relateful/shoal/blob/main/docs/guide/dashboards.md),
|
|
198
|
+
[metrics and Grafana](https://github.com/relateful/shoal/blob/main/docs/guide/metrics.md), [diagnosing a slow bot](https://github.com/relateful/shoal/blob/main/docs/guide/diagnostics.md),
|
|
199
|
+
[architecture](https://github.com/relateful/shoal/blob/main/docs/guide/architecture.md),
|
|
200
|
+
[migrating from discord.py](https://github.com/relateful/shoal/blob/main/docs/guide/migrating-from-discord-py.md).
|
|
201
|
+
- The API reference is generated from the docstrings: `uv run --group docs mkdocs serve`.
|
|
202
|
+
- [Discord API coverage](https://github.com/relateful/shoal/blob/main/docs/coverage.md) lists every endpoint Discord documents and the Shoal method
|
|
203
|
+
for it, generated from Discord's docs and checked weekly.
|
|
204
|
+
|
|
205
|
+
## Quality
|
|
206
|
+
|
|
207
|
+
- Every module with behaviour has a test module, and coverage is held above 95%.
|
|
208
|
+
- `tests/conventions.py` enforces the project's rules across the whole tree: docs links, ordering,
|
|
209
|
+
constants defined once, slots, exports, test layout.
|
|
210
|
+
- CI runs on Python 3.12, 3.13 and 3.14, on Linux, Windows and macOS.
|
|
211
|
+
|
|
212
|
+
## Contributing
|
|
213
|
+
|
|
214
|
+
See [CONTRIBUTING.md](https://github.com/relateful/shoal/blob/main/CONTRIBUTING.md) for the rules, the layout and every check CI runs.
|
|
215
|
+
|
|
216
|
+
## License
|
|
217
|
+
|
|
218
|
+
[MIT](https://github.com/relateful/shoal/blob/main/LICENSE)
|