Skip to main content

Async, type-safe Zulip bot development framework

Project description

🤖 Async Zulip Bot SDK

Async, type-safe Zulip bot development framework

Python 3.12+ License GitHub release

English | 中文


✨ Features

  • 🚀 Async-First — Built on httpx.AsyncClient for high-performance async operations, fully compatible with official zulip.Client interface
  • 📝 Type-Safe — Complete type hints and automatic validation with Pydantic v2 models
  • 🎯 Command System — Powerful built-in command parser with type checking, argument validation, and auto-generated help
  • 💾 Flexible Storage — Choose between lightweight JSON storage or full SQLAlchemy ORM with Alembic migrations
  • 🌐 Internationalization — Built-in i18n support with JSON-based translation files
  • 🔧 YAML Configuration — Single source of truth for bot settings in bot.yaml
  • 🖥️ Interactive Console — Beautiful Rich-based TUI for managing multiple bots with live logs and command history
  • 📦 Production-Ready — Long-polling event loop, automatic reconnection, and error recovery built-in

📦 Installation

From version v0.2.0 and later, the SDK is published to PyPI via an automated GitHub Actions workflow whenever a GitHub release is created.

Option 1: Install from PyPI (recommended for users)

# Using uv (recommended)
uv pip install async-zulip-bot-sdk

# Or using pip directly
pip install async-zulip-bot-sdk

Option 2: Install from source (for development)

git clone https://github.com/Open-LLM-VTuber/async-zulip-bot-sdk.git
cd async-zulip-bot-sdk

# Using uv (recommended)
uv venv
uv pip install -e .

# Or using venv + pip
python -m venv venv
venv\Scripts\activate  # Windows
source venv/bin/activate  # macOS/Linux
pip install -e .

🚀 Quick Start

⚠️ Breaking change (since v1.0.0): bot configuration now lives in each bot's bot.yaml. Class-level attributes (e.g., command_prefixes, enable_storage, enable_orm) are ignored. Set prefixes/mention/help/storage/ORM options in the bot's YAML instead of subclass attributes.

1. Configure Zulip Credentials

Download your zuliprc file:

You can create or regenerate your API Key in Settings - Personal - Account & privacy, enter your password, and select Download zuliprc. Place each bot's file under its own folder, e.g. bots/echo_bot/zuliprc.

2. Configure bots.yaml

Create a bots.yaml file at the root of project, you can refer to bots.yaml.example for details. Define which bots to launch and where to find them:

bots:
  - name: echo_bot
    module: bots.echo_bot
    class_name: BOT_CLASS
    enabled: true
    # Optional override; defaults to bots/<name>/zuliprc
    # zuliprc: bots/echo_bot/zuliprc
    config: {}  # optional per-bot config passed to factory (second arg)

3. Configure per-bot settings (bot.yaml)

Create bots/echo_bot/bot.yaml to set prefixes/mentions/help/storage/ORM:

command_prefixes:
- "!"
enable_mention_commands: true
auto_help_command: true
enable_storage: true
# storage_path: bot_data/echo_bot.db
enable_orm: false
# orm_db_path: bot_data/echo_bot.sqlite
language: en

4. Create Your First Bot

import asyncio

from bot_sdk import (
    BaseBot,
    BotRunner,
    Message,
    CommandSpec,
    CommandArgument,
    setup_logging,
)

class MyBot(BaseBot):
    def __init__(self, client):
        # BaseBot will receive a bot-specific logger when used via BotRunner / console
        super().__init__(client)
        # Register commands (prefixes come from bot.yaml)
        self.command_parser.register_spec(
            CommandSpec(
                name="echo",
                description="Echo back the provided text",
                args=[CommandArgument("text", str, required=True, multiple=True)],
                handler=self.handle_echo,
            )
        )
    
    async def on_start(self):
        """Called when bot starts"""
        # Prefer structured logging over print
        self.logger.info("Bot started! user_id={}", self._user_id)
    
    async def handle_echo(self, invocation, message, bot):
        """Handle echo command"""
        text = " ".join(invocation.args.get("text", []))
        await self.send_reply(message, f"Echo: {text}")
    
    async def on_message(self, message: Message):
        """Handle non-command messages"""
        self.logger.debug("Incoming message: {}", message.content[:50])
        await self.send_reply(message, "Try !help to see available commands!")

BOT_CLASS = MyBot

Remember to save this code in a __init__.py file under the directory your configured in bots.yaml. In this example, you would save it as bots/echo_bot/__init__.py.

5. Run Your Bots

You can use this SDK in your own project directory (not necessarily this repo). A typical layout looks like:

my-zulip-bots/
    bots.yaml
    bots/
        echo_bot/
            __init__.py
            bot.yaml
            zuliprc

Interactive Console

The SDK comes with a built-in interactive console for managing bots, featuring a TUI (Text User Interface) powered by rich.

Run the console (recommended):

# Activate your virtual environment first
# .venv\Scripts\activate  # Windows
# source .venv/bin/activate  # macOS/Linux

async-zulip-bot               # runs in the current project directory

This command looks for bots.yaml and the bots/ package in the current working directory, so run it from your own project root.

Features:

  • Rich TUI: Beautiful, split-screen layout for logs, status, and input.
  • Command History: Use Up/Down arrows to navigate previous commands.
  • Log Scrolling: Use PageUp/PageDown to scroll through logs.
  • Bot Management: Run, stop, and reload bots dynamically.
  • Tab Completion: Press Tab to auto-complete commands and bot names.

After entering the interactive console, use the run command to start your bot:

bot-console> run echo_bot

Remember to use tab completion for faster typing!

For more commands, type help in the console.

📚 Core Concepts

AsyncClient

Fully async Zulip API client mirroring the official zulip.Client interface:

from bot_sdk import AsyncClient

async with AsyncClient(config_file="zuliprc") as client:
    # Get user profile
    profile = await client.get_profile()
    
    # Send messages
    await client.send_message({
        "type": "stream",
        "to": "general",
        "topic": "Hello",
        "content": "Hello, world!"
    })
    
    # Get subscriptions
    subs = await client.get_subscriptions()

Command System

Type-safe command definitions with automatic validation:

from bot_sdk import CommandSpec, CommandArgument

# Define commands with arguments
self.command_parser.register_spec(
    CommandSpec(
        name="greet",
        description="Greet a user",
        args=[
            CommandArgument("name", str, required=True),
            CommandArgument("times", int, required=False),
        ],
        handler=self.handle_greet,
    )
)

async def handle_greet(self, invocation, message, bot):
    name = invocation.args["name"]
    times = invocation.args.get("times", 1)
    greeting = f"Hello, {name}! " * times
    await self.send_reply(message, greeting)

Auto-generated help:

Use !help or !? to automatically show all registered commands and arguments.

Lifecycle Hooks

class MyBot(BaseBot):
    async def on_start(self):
        """Called when bot starts"""
        pass
    
    async def on_stop(self):
        """Called when bot stops"""
        pass
    
    async def on_message(self, message: Message):
        """Called for non-command messages"""
        pass

🔧 Advanced Usage

Custom Command Prefixes and Mention Detection

Configuration for prefixes and mention commands is now read from bot.yaml (per-bot YAML config). Class-level attributes are ignored.

command_prefixes:
- "!"
- "/" # Not recommended as '/' is zulip official command prefix
enable_mention_commands: true  # Enable @bot to trigger commands
auto_help_command: true        # Auto-register built-in help command

Typed Message Models

from bot_sdk import Message, StreamMessageRequest

async def on_message(self, message: Message):
    # Full type hints
    sender = message.sender_full_name
    content = message.content
    
    # Send typed messages
    await self.client.send_message(
        StreamMessageRequest(
            to=message.stream_id,
            topic="Reply",
            content="Typed reply!"
        )
    )

📚 Documentation

Comprehensive API documentation is available:

Documentation includes:

  • 📖 Quick Start Guide
  • 🔧 API Reference (AsyncClient, BaseBot, BotRunner)
  • 💬 Command System
  • 📊 Data Models
  • ⚙️ Configuration Management
  • 📝 Logging

🤝 Contributing

Contributions are welcome! Feel free to submit Pull Requests.

Contributing Documentation: We welcome documentation contributions in both Chinese and English.

🙏 Credits & Notices

📄 License

MIT License - see LICENSE file for details


Made with ❤️ for the Open-LLM-VTuber Zulip team

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

async_zulip_bot_sdk-1.3.0.tar.gz (51.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

async_zulip_bot_sdk-1.3.0-py3-none-any.whl (59.4 kB view details)

Uploaded Python 3

File details

Details for the file async_zulip_bot_sdk-1.3.0.tar.gz.

File metadata

  • Download URL: async_zulip_bot_sdk-1.3.0.tar.gz
  • Upload date:
  • Size: 51.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for async_zulip_bot_sdk-1.3.0.tar.gz
Algorithm Hash digest
SHA256 973850d03e63e1f911dab233b0f5bd83a6291f70acb3c9ce5de791a93779c0b9
MD5 af85156d154fb2999b4825474f390c40
BLAKE2b-256 efc47a488053e8f4de5e49a16165f176413d1ba29d8dc497572d2cebf368b24f

See more details on using hashes here.

Provenance

The following attestation bundles were made for async_zulip_bot_sdk-1.3.0.tar.gz:

Publisher: python-publish.yml on Open-LLM-VTuber/async-zulip-bot-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file async_zulip_bot_sdk-1.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for async_zulip_bot_sdk-1.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 23fe5cede26347c087dfcf1f8e95bd053fe05511509a0bb218c04f013348df5e
MD5 4d86bd23ac7156c1eaf877f63c3b35b6
BLAKE2b-256 881a1d8283050775a00451257bb97f51b9515b2d7997090e07c88302b918e398

See more details on using hashes here.

Provenance

The following attestation bundles were made for async_zulip_bot_sdk-1.3.0-py3-none-any.whl:

Publisher: python-publish.yml on Open-LLM-VTuber/async-zulip-bot-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page