slimbots
A discord.py-shaped bot framework for slim-m: a Bot() constructor, @bot.command, ctx, and a live Space/Canvas model, plus the safeguards seven real bots each used to rebuild by hand (bot-ignore, cooldowns, permission gates, clean lifecycle, a supervised background task).
pip install slim-m
The distribution is named slim-m on PyPI; the import stays slimbots. bot-casino in the slim-bots repo is the reference port built on this; docs/framework.md there is the deeper reference for everything this page only shows the shape of.
A minimal bot
import os
from slimbots import Bot
bot = Bot(prefix="!")
@bot.command(help="Answer pong")
async def ping(ctx):
await ctx.reply("pong")
if __name__ == "__main__":
raise SystemExit(bot.run() or 0)
Bot() reads SLIMM_URL, SLIMM_BOT_TOKEN, and SLIMM_CHANNELS (comma-separated channel ids) itself - nothing above needs import os just to wire those up. Run it with:
SLIMM_URL=https://your.space SLIMM_BOT_TOKEN=slimbot_... SLIMM_CHANNELS=<channel-uuid> python3 bot.py
A bot token is minted by an admin in the Bots section of Space settings, is shown once, and does not rotate. A 401 is terminal - bot.run() treats a revoked token as a reason to stop, not retry.
Commands
@bot.command registers async def name(ctx, *typed_args). Arguments convert from the function's own annotations:
from slimbots import Member
@bot.command(help="Say hi to someone")
async def greet(ctx, member: Member, text: str = "hello"):
await ctx.reply(f"{text}, {member.display_name}!")
int/floatconvert one token, or reply with a clearBadArgumentinstead of a traceback.stris one token, unless it is the last parameter, in which case it consumes the rest of the message (textabove).Memberresolves@usernameor a bare username/id againstbot.space.Duration(10m,2h30m) andTimeOfDay(14:30) convert a token into seconds or an hour/minute pair - built for exactly the reminder-bot shape.- A parameter with a default is optional; a missing required one names itself in the reply.
help is generated automatically from whatever gets registered, unless a bot defines its own help command first.
Command groups
A group dispatches its first argument token to a registered subcommand, the same shape !remind in 10m <text> needs:
@bot.group(help="Schedule a reminder")
async def remind(ctx, rest: str = ""):
await ctx.reply("try `!remind in <duration> <text>` or `!remind at <HH:MM> <text>`")
@remind.command(name="in", usage="<duration> <text>")
async def remind_in(ctx, duration, text: str):
await ctx.reply(f"in {int(duration)}s: {text}")
An unrecognised or bare invocation falls back to the group's own function. bot-reminders in the templates repo is the worked example.
Settings
bot.setting(name, default=None, *, type=str, required=False) reads one of a bot's own env vars the same way Bot reads its three, converting via type (int, float, or list for a comma-separated one):
JELLYFIN_URL = bot.setting("JELLYFIN_URL", required=True)
JELLYFIN_POLL_SECONDS = bot.setting("JELLYFIN_POLL_SECONDS", 300, type=int)
A missing required=True value is never raised at the setting() call itself; it is collected and reported together with a missing SLIMM_URL/token/channels in one clear error when bot.start() runs. Bot(default_data_path="mybot.db") gives bot.data_path - the one place a bot's own sqlite file lives, derived from SLIMM_DB_PATH or that default.
Background tasks
bot.background(coro, name=None) is the only way a bot should start a loop that outlives one command:
@bot.event
async def on_ready():
bot.background(poll_loop(), name="poll")
async def poll_loop():
while True:
await do_the_poll()
await asyncio.sleep(300)
A plain asyncio.create_task(...) is only weakly referenced by the event loop and can be silently garbage-collected mid-run; bot.background holds a strong reference for as long as it runs. An unhandled exception in it (a 401 included) is treated as fatal: logged, the bot's main loop cancelled, and bot.run() exits non-zero so a container restarts it, instead of the loop's failure going unnoticed while the rest of the bot carries on.
Embeds
from slimbots import Embed
await ctx.reply(embed=Embed(title="Balance").add_field("chips", str(balance)))
Embed serializes to slim-m's real wire shape and sends for real; if an older server rejects the field as unknown, the framework retries once as the embed's own rendered markdown instead, so a bot degrades rather than breaking.
The Space model
bot.space is members, channels, roles as live dicts, refreshed on connect:
member = bot.space.get_member("nick")
await bot.space.grant_role(member, role)
member.has_permission(permission) checks the caller's base permissions - never the bot's own, which can hold anything a human member can, including ADMINISTRATOR.
Testing
slimbots.testing.FakeAsyncClient never touches the network:
from slimbots.testing import FakeAsyncClient
client = FakeAsyncClient(me_id="bot-1")
bot.client = client
await bot.process_message({"id": "m1", "author_id": "u1", "channel_id": "c1", "content": "!ping"})
assert client.sent[-1]["content"] == "pong"
Pre-stubbed routes (/me, /channels, /members, /roles, /bots/commands) work out of the box; stub anything else with client.respond(method, path, response_or_exception) - an unstubbed call raises immediately rather than hanging, so a forgotten stub fails the test loudly at the call it forgot, not a confusing assertion three lines later.
Where to go deeper
docs/framework.md and the seven bot templates in slim-bots cover the rest: command registration with the server, channel scoping and durable cursors, cooldowns and permission gates, the Canvas model for the Voice Canvas, typed event dispatch, and every safeguard's own reasoning.
Installing an unreleased version
pip install "slim-m @ git+https://github.com/NC1107/slim-bots.git@main#subdirectory=slimbots"
Each template's requirements.txt pins one of the two install lines above.
Licence
PolyForm Noncommercial 1.0.0 - see LICENSE in this package, or the full text.
Release files for slim-m 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| slim_m-0.4.0.tar.gz | 52.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| slim_m-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 96.0 kB
Release files / slim_m-0.4.0.tar.gz
| Download URL | slim_m-0.4.0.tar.gz |
|---|---|
| Size | 52.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
23a84903daad99ce1504fef801755e38ceb03722be1d380877c4bdd9059362d6
|
|
BLAKE2b-256 checksum How to use checksums |
2d9314c6a43591b4e8ea909b66cd4c45cdecdd420e063ba7b98cf5ffbc9ff154
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / slim_m-0.4.0-py3-none-any.whl
| Download URL | slim_m-0.4.0-py3-none-any.whl |
|---|---|
| Size | 43.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
211ea98ff42cb2bf06f25e5046869f1a1389262a74a80efabf3df438ae82696f
|
|
BLAKE2b-256 checksum How to use checksums |
7590d98c2bee15cd45061c4a3b6206403ca82a8d47b4aa3798c2cba5e551b7fa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency log