BaseBot is the base class for all Zulip bots. It handles message dispatch, command parsing, and replies.
from bot_sdk import BaseBot
from bot_sdk import BaseBot, Message
class MyBot(BaseBot):
async def on_message(self, message: Message) -> None:
await self.send_reply(message, "Received!")
bot.yaml (fields in BotLocalConfig).command_prefixes, enable_mention_commands, auto_help_command, enable_storage, enable_orm, etc.) are no longer read. Set them in YAML instead (see docs/config.md).Built-ins registered by default (still enabled unless you disable auto_help_command in YAML):
whoami: show caller roles/level.perm: manage permissions (bot owner, role levels, allow/deny stop); requires min_level=200 (bot_owner).reload: reload bot.yaml settings and i18n translations without restart; requires min_level=50 (admin).stop: request a graceful BotRunner shutdown (permission-checked); requires min_level=50 (admin).Permission enforcement: if a CommandSpec has min_level, BaseBot checks it before dispatch. Early permission denial is returned before argument parsing, so users see “Permission denied” rather than “Missing argument”.
Built-in commands use i18n, so descriptions and success/error messages are localized based on the bot’s configured language.
AsyncClient instance.CommandParser instance, rebuilt using values from bot.yaml."en", "zh"). Loaded from bot.yaml and used by i18n.I18n instance for translating user-facing strings. Initialized during post_init().BotLocalConfig instance (per-bot YAML configuration).bot.yaml and apply config (prefixes/mentions/help/storage/ORM).min_level if present, then dispatches; otherwise calls on_message().CommandInvocation | None: Parse message as command.BaseBot automatically initializes an i18n system during post_init():
bot.yaml (field language, default "en")<bot_module_dir>/i18n/{language}.json (bot-specific overrides)bot_sdk/i18n/{language}.json (SDK defaults; fallback to English)self.tr() for all user-visible stringsself.tr() for multi-language supportExample:
class MyBot(BaseBot):
def register_commands(self):
self.command_parser.register_spec(
CommandSpec(
name="greet",
description=self.tr("Greet the user"), # Translatable at registration time
handler=self.handle_greet,
)
)
async def handle_greet(self, inv, message, bot):
# User-facing strings are translated
await self.send_reply(message, self.tr("Hello, {name}!", name=message.sender_full_name))
See docs/i18n.md (if available) for detailed i18n setup and per-bot translation files.
from bot_sdk import BaseBot, Message, run_bot
class SimpleBot(BaseBot):
async def on_message(self, message: Message) -> None:
await self.send_reply(message, f"Echo: {message.content}")
if __name__ == "__main__":
run_bot(SimpleBot)
from bot_sdk import BaseBot, Message, CommandSpec, CommandArgument
class TodoBot(BaseBot):
def __init__(self, client):
super().__init__(client)
self.todos = []
self.command_parser.register_spec(
CommandSpec(
name="add",
description="Add todo",
args=[CommandArgument(name="task", type=str, multiple=True)],
handler=self.handle_add,
)
)
self.command_parser.register_spec(
CommandSpec(name="list", description="List todos", handler=self.handle_list)
)
self.command_parser.register_spec(
CommandSpec(
name="done",
description="Mark done",
args=[CommandArgument(name="index", type=int, required=True)],
handler=self.handle_done,
)
)
async def handle_add(self, invocation, message, bot):
task = " ".join(invocation.args["task"])
self.todos.append(task)
await self.send_reply(message, f"✅ Added: {task}")
async def handle_list(self, invocation, message, bot):
if not self.todos:
await self.send_reply(message, "No todos")
return
lines = [f"{i+1}. {task}" for i, task in enumerate(self.todos)]
await self.send_reply(message, "\n".join(lines))
async def handle_done(self, invocation, message, bot):
idx = invocation.args["index"] - 1
if 0 <= idx < len(self.todos):
task = self.todos.pop(idx)
await self.send_reply(message, f"✅ Done: {task}")
else:
await self.send_reply(message, "❌ Invalid index")
async def on_message(self, message: Message) -> None:
await self.send_reply(message, "Use /help to see commands")
super() when overriding lifecycle hooks.on_message / handlers and report user-friendly errors.loguru.