English | 简体中文 | 繁體中文 | 日本語 | Русский
Write once, deploy to QQ / Telegram / Kook / Yunhu / WeChat Public Account / OneBot12 / ... multiple platforms.
An event-driven multi-platform chatbot development framework.
Based on the OneBot12 standard interface, write once and deploy to multiple platforms; with a flexible plugin system, hot reload support, and a complete developer toolchain, suitable for scenarios ranging from simple chatbots to complex automation systems.
ErisPulse uses an adapter layer to abstract platform differences, allowing business code to focus solely on events:
graph LR
subgraph Platforms[Platforms]
QQ["QQ"]
TG["Telegram"]
Kook["Kook"]
YH["Yunhu"]
WX["WeChat Public Account"]
end
subgraph Adapters[Adapter Layer]
A1["QQ Adapter"]
A2["Telegram Adapter"]
A3["Kook Adapter"]
A4["Yunhu Adapter"]
A5["WeChat Adapter"]
end
Event["Event Bus<br/>Middleware → Dispatch command/message/notice/request/meta"]
subgraph Modules[Business Modules]
M1["Command Handler<br/>@command"]
M2["Message Handler<br/>@message"]
M3["Your Module"]
end
QQ --> A1
TG --> A2
Kook --> A3
YH --> A4
WX --> A5
A1 -->|"OB12 Event"| Event
A2 -->|"OB12 Event"| Event
A3 -->|"OB12 Event"| Event
A4 -->|"OB12 Event"| Event
A5 -->|"OB12 Event"| Event
Event -->|"Dispatch"| M1
Event -->|"Dispatch"| M2
Event -->|"Dispatch"| M3
M1 -.->|"event.reply()<br/>SendDSL"| Event
Event -.->|"Send"| A1
- Adapter Layer converts native platform protocols into OneBot12 standard events, so business modules are unaware of platform differences.
- Event Bus executes the middleware chain first, then dispatches events to five types of handlers based on event type.
- Your Code subscribes to events using decorators, and replies using
event.reply()or SendDSL. Replies flow back through the same path to the platform.
For a detailed overview of the module composition, initialization process, and lifecycle events, see Architecture Overview.
The installation script automatically detects your environment (Docker, Python, uv), guides you to the most suitable installation method, and supports multiple languages (Chinese/English/Japanese/Russian/Traditional Chinese).
Windows (PowerShell):
irm https://get.erisdev.com/install.ps1 -OutFile install.ps1; powershell -ExecutionPolicy Bypass -File install.ps1macOS / Linux:
curl -fsSL https://get.erisdev.com/install.sh -o install.sh && chmod +x install.sh && ./install.sh|
Docker Installation Demo install_pip.mp4 |
pip Installation Demo install_docker.mp4 |
docker pull erispulse/erispulse:latestDocker Hub Unavailable?
If Docker Hub is inaccessible, you can use GitHub Container Registry:
docker pull ghcr.io/erispulse/erispulse:latestWhen using the ghcr.io image, modify docker-compose.yml to update the image:
image: ghcr.io/erispulse/erispulse:latestQuick Start
# Download docker-compose.yml
curl -O https://raw.githubusercontent.com/ErisPulse/ErisPulse/main/docker-compose.yml
# Set Dashboard login token and start
ERISPULSE_DASHBOARD_TOKEN=your-token docker compose up -dAfter starting, access http://<host>:8000/Dashboard and log in using the set token.
The image includes the ErisPulse framework and Dashboard management panel, supporting
linux/amd64andlinux/arm64architectures.Persistence: Configuration files and installed modules/adapters are persisted to the host via volume mounts, so they are not lost after container restart. Framework updates are completed through Dashboard hot updates.
Docker Environment Variables
| Variable | Default | Description |
|---|---|---|
ERISPULSE_DASHBOARD_TOKEN |
empty | Dashboard login token (automatically written to config when set) |
ERISPULSE_PORT |
8000 |
Dashboard port mapping |
ERISPULSE_TAG |
latest |
Image tag, can be set to dev for pre-release images |
ERISPULSE_BUILD_TARGET |
production |
Build target: production (stable) or dev (pre-release) |
CONTAINER_NAME |
erispulse |
Container name |
TZ |
Asia/Shanghai |
Container timezone |
LANG |
en_US.UTF-8 |
System language, automatically detects startup interface language |
ERISPULSE_LANG |
empty | Force startup interface language: zh / zh_TW / en / ja / ru (overrides LANG) |
Install ErisPulse with one click via the 1Panel app store, see ErisPulse-1Panel.
bash <(curl -sL https://get-1panel.erisdev.com/install.sh)ErisPulse is available in the 1Panel third-party app store, and can be installed using the okxlin/appstore third-party repository.
pip install ErisPulseYou can also use the one-click installation script above, which automatically detects the environment and guides configuration.
# Interactive initialization
epsdk init
# Quick initialization (specify project name)
epsdk init -q -n my_botCreate a main.py file:
|
Command Handler from ErisPulse import sdk
from ErisPulse.Core.Event import command
@command("hello", help="Send greeting message")
async def hello_handler(event):
user_name = event.get_user_nickname() or "friend"
await event.reply(f"Hello, {user_name}!")
@command("ping", help="Test if bot is online")
async def ping_handler(event):
await event.reply("Pong! Bot is running normally.")
if __name__ == "__main__":
import asyncio
asyncio.run(sdk.run(keep_running=True)) |
Effect Explanation Send Bot replies: Send Bot replies: Running Method epsdk run main.py
# Or in development mode
epsdk run main.py --reload |
For more detailed instructions, see:
Identical command handlers. Different platforms. No changes to business logic.
|
Kook
|
|
Yunhu
|
A single chain call completes all sending logic, including @, reply, retry, timeout, and callback:
yunhu = sdk.adapter.get("yunhu")
# Single send: @user + reply + retry + success callback
await (yunhu.Send.To("group", "123")
.At("456").Reply("msg_789")
.Retry(3).Timeout(10)
.Hook(lambda r: print("Send successful!"))
.Text("Hello"))
# Bulk send: one chain sends multiple messages
results = await (yunhu.Send.To("user", "123")
.Build()
.Text("Notification 1")
.Image("pic.jpg")
.Retry(2)
.send_all())Supports Hook (success callback), Retry (failure retry), Timeout (timeout cancel), OnProgress (progress monitoring), Defer (delayed send), Build (bulk build), and other chainable methods. See SendDSL Documentation.
ErisPulse includes a powerful multi-turn conversation engine, making it easy to implement guided operations and information collection scenarios:
from ErisPulse.Core.Event import command, request
@command("register")
async def register_handler(event):
conv = event.conversation(timeout=60)
await conv.say("Welcome to register!")
# Multi-step collection of user information, with automatic validation
data = await conv.collect([
{"key": "name", "prompt": "Please enter your name"},
{"key": "age", "prompt": "Please enter your age",
"validator": lambda e: e.get_text().strip().isdigit(),
"retry_prompt": "Age must be a number, please re-enter"},
])
if data and await conv.confirm(f"Confirm registration? Name: {data['name']}, Age: {data['age']}"):
# Push notification using SendDSL
await sdk.adapter.get(event.get_platform()).Send.To(
"user", event.get_user_id()
).Text(f"Registration successful! Welcome {data['name']}")
# Or await event.reply("Registration successful!")
# Automatically handle friend requests
@request.on_friend_request()
async def handle_friend_request(event):
user_name = event.get_user_nickname() or event.get_user_id()
# Approve the request
result = await event.approve()
if result.get("status") == "ok":
await event.reply(f"Friend request approved automatically, welcome {user_name}")See More Conversation API (Branching / Selection / Persistence)
@command("quiz")
async def quiz_handler(event):
conv = event.conversation(timeout=30)
# Option-based question
answer = await conv.choose("Who is the creator of Python?", [
"Guido van Rossum",
"James Gosling",
"Dennis Ritchie",
])
if answer == 0:
await conv.say("Correct!")
elif answer is None:
await conv.say("Timed out, try again next time!")
else:
await conv.say("Incorrect, the correct answer is Guido van Rossum")
@command("menu")
async def menu_handler(event):
conv = event.conversation(timeout=60)
# Branching, building complex interaction flow
@conv.branch("main")
async def main_menu():
await conv.say("=== Main Menu ===\n1. Personal Info\n2. Settings\n3. Exit")
resp = await conv.wait()
if resp and resp.get_text().strip() == "1":
await conv.goto("profile")
@conv.branch("profile")
async def profile():
await conv.say("Name: Alice\n0. Return")
resp = await conv.wait()
if resp and resp.get_text().strip() == "0":
await conv.goto("main")
await conv.start()ErisPulse provides a complete multi-platform chatbot development toolchain, with each core module serving its own purpose:
graph TB
SDK["sdk<br/>Unified Entry"]
SDK --> Event["Event<br/>Event System"]
SDK --> AdapterMgr["Adapter<br/>Adapter Management"]
SDK --> ModuleMgr["Module<br/>Module Management"]
SDK --> Router["Router<br/>HTTP/WS Routing"]
SDK --> Storage["Storage<br/>SQLite Storage"]
SDK --> Config["Config<br/>Configuration Management"]
SDK --> Lifecycle["Lifecycle<br/>Lifecycle"]
SDK --> Logger["Logger<br/>Logging System"]
SDK --> Client["HttpClient<br/>HTTP Client"]
| Module | Description |
|---|---|
| Event | Event system, providing command / message / notice / request / meta event types + Conversation multi-turn dialogue |
| Adapter | Adapter management, BaseAdapter base class for unified event conversion and SendDSL sending, supporting 15+ platforms including QQ / Telegram / Kook / Yunhu / WeChat Public Account |
| Module | Module management, BaseModule base class + dependency declaration and topological sorting for loading |
| SendDSL | Chainable sending, complex logic such as @/reply/retry/timeout/batch in one line |
| Router | HTTP/WebSocket routing system (FastAPI + Uvicorn) |
| Storage | SQLite-based key-value storage + general SQL chain query |
| Config | TOML configuration management |
| Lifecycle | Lifecycle event hooks (core.init / adapter.* / module.*) |
| Logger | Modular logging system, supports sub-loggers |
| HttpClient | Unified HTTP/WS client (based on aiohttp), with built-in retry and ErisPulse exception system |
For more design details (initialization process, lifecycle events, module loading strategy), see Architecture Overview.
Without modifying any module code, declare "what is effective in what scope" in the configuration:
[ErisPulse.scope.platforms.onebot11]
modules = ["Chat", "Tool*"] # ① Module level: only these modules are open on this platform (glob / regex)
[ErisPulse.scope.identity.users.onebot11]
deny = ["u_bad", "spam_*"] # ② Identity level: events from blacklisted users are discarded
[ErisPulse.scope.actions.MyModule]
send = { allow = ["Text"] } # ③ Outbound level: this module only allows sending text
api = { deny = ["set_*", "leave_*"] } # and prohibits management APIs# Runtime also modifiable, immediate effect (supports dictionary-style read/write via dot notation)
sdk.scope.set_action("MyModule", "api", deny=["set_*"])See Scope
# Override message handler trigger conditions (AND with code conditions; supports all types: meta/message/notice/request/command)
[ErisPulse.event.overrides.message.ChatModule]
pattern = "闲聊*"
# Override command implementation parameters (master / hidden / aliases / prefix, user priority)
[ErisPulse.event.overrides.command.MyModule.restart]
master = trueSee Event Override
ErisPulse is not just a framework. Start using it right away, no need to build wheels from scratch.
|
Framework Core runtime Unified event & message model |
Dashboard Visual management Plugins · Logs · Configuration |
AI Builder Natural language → usable module |
Module Market Ready-to-use plugins |
|
Adapters 15+ platform integrations |
ErisPulse-App Official multi-platform client Mobile directly run · Desktop tray always on |
Docker Multi-architecture support
|
Documentation & CLI
|
We welcome contributions to adapters! Not sure where to start? See Contribution Guide.
| Adapter | Description |
|---|---|
| Kook (open black room) instant messaging platform | |
| Matrix decentralized communication protocol | |
| OneBot v11 general robot protocol | |
| OneBot v12 standard protocol | |
| QQ official robot platform | |
| Web terminal debugging, no need to connect to real platform | |
| Command line chat, zero configuration development and debugging | |
| Global instant messaging platform | |
| Email protocol adapter | |
| Enterprise-level instant messaging platform (robot access) | |
| User protocol-based Yunhu adapter | |
| HuaFeng Coffee House | Allons! (・ω・) / |
| Global community communication platform, supports servers, channels, and private messages | |
| General HTTP bridge adapter, connects to any system | |
| Official WeChat public account platform |
See Adapter Details
Connect with us:
- Telegram: https://t.me/ErisPulse
- QQ Group: https://qm.qq.com/q/TOwnCmypcy
- Yunhu Group: https://yhfx.jwznb.com/share?key=VWJL4fTWXepa&ts=1781889199
The health of the ErisPulse project still needs your contribution! We welcome contributions in various forms:
- Report Issues — Submit bug reports in GitHub Issues
- Feature Requests — Propose new ideas through Community Discussions
- Code Contributions — Read Code Style and Contribution Guidelines before submitting PRs
- Documentation Improvements — Help improve documentation and example code
First-time contributor? Start here 👉 First Contribution Practice
Some code in this project is based on sdkFrame.
The core adapter standardization layer is referenced and benefited from the OneBot12 specification.
Special thanks to the Yunhu ecosystem and community.
The early exploration and growth of ErisPulse would not have been possible without the support of the Yunhu developer community, many ideas, adapters, and practical experiences were born here.
We also thank all developers and project authors who have contributed to ErisPulse, OneBot, and the open-source community.



