Skip to main content

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 / float convert one token, or reply with a clear BadArgument instead of a traceback.
  • str is one token, unless it is the last parameter, in which case it consumes the rest of the message (text above).
  • Member resolves @username or a bare username/id against bot.space.
  • Duration (10m, 2h30m) and TimeOfDay (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)

Source distribution for slim-m 0.4.0
File Size Uploaded
slim_m-0.4.0.tar.gz 52.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for slim-m 0.4.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page