How to Implement Custom Bot Commands Using the `on_command` Decorator in HoshinoBot
Use the sv.on_command decorator from a Service instance to register async command handlers with built-in permission checks, group-enable logic, and automatic NoneBot integration.
HoshinoBot organizes functionality into modular services that simplify command registration and permission management. To add custom bot commands, developers leverage the on_command decorator implemented in hoshino/service.py (lines 86-116) of the ice9coffee/hoshinobot repository. This decorator wraps handlers with group verification, service state checks, and logging while maintaining compatibility with NoneBot's underlying command system.
Understanding the Service Architecture
HoshinoBot uses the Service class as a container for related commands and handlers. Each service manages its own enable/disable state per group and provides the on_command decorator for registration.
In hoshino/service.py, the Service class initializes with metadata that controls visibility and default behavior:
from hoshino import Service
sv = Service('module-name', help_='Command description', bundle='Category')
The arena module demonstrates this pattern in practice at hoshino/modules/priconne/arena/__init__.py (lines 19-21), where it creates a service instance before registering game-related commands.
Implementing the on_command Decorator
The on_command decorator in hoshino/service.py (lines 86-116) wraps your async function with HoshinoBot's permission infrastructure. When invoked, the wrapper performs four critical validations before executing your handler:
- Group verification – Confirms the message originates from a group chat
- Service state check – Verifies the service is enabled for the specific group
- Mention validation – Respects the
only_to_meflag when present - Logging – Records execution success or failure for debugging
To implement a command, decorate an async function that accepts a CommandSession parameter:
@sv.on_command('ping')
async def ping_cmd(session):
await session.send('Pong!')
Configuration Options and Parameters
The sv.on_command decorator accepts several HoshinoBot-specific parameters alongside standard NoneBot arguments:
- aliases – Tuple of alternative command triggers (e.g.,
aliases=('weather', '天气预报')) - only_to_me – Boolean flag requiring the bot to be mentioned (
only_to_me=True) - deny_tip – Custom message displayed when the service is disabled for the group
- priority and permission – Passed directly to NoneBot's underlying command registry
The decorator forwards additional keyword arguments to nonebot.on_command, maintaining full access to NoneBot's native configuration options.
Complete Code Examples
Basic Ping Command
Create a file at hoshino/modules/example/ping.py:
from hoshino import Service
sv = Service('example-ping', help_='A demo ping command', bundle='Demo')
@sv.on_command('ping', aliases=('test',), only_to_me=False)
async def ping(session):
"""Reply with 'Pong!' when users type '/ping' or '/test'."""
await session.send('Pong!')
Super-User Exclusive Commands
For administrative commands restricted to bot owners, use the sucmd helper from hoshino/service.py:
from hoshino.service import sucmd
from hoshino import priv
@sucmd('secret', force_private=True, permission=priv.SUPERUSER)
async def secret(session):
"""Accessible only to superusers in private chat."""
await session.send('🔑 Admin access granted')
Interactive Commands with Disable Notifications
Implement user input prompts and custom disable messages:
@sv.on_command('weather', aliases=('天气',), only_to_me=True,
deny_tip='天气查询已在本群关闭,请联系管理员')
async def weather(session):
"""Fetch weather data with interactive prompts."""
city = session.get('city', prompt='要查询哪个城市的天气?')
await session.send(f'{city} 今天晴朗,气温 23℃')
Summary
- Service instances manage command grouping and permission states; instantiate with
Service(name, help_, bundle)before registering commands. - The
sv.on_commanddecorator (lines 86-116 inhoshino/service.py) automatically handles group validation, service enable checks, and NoneBot registration. - Use
aliasesto define command shortcuts,only_to_meto restrict mentions, anddeny_tipto customize disable notifications. - The
sucmdhelper provides shorthand syntax for superuser-only administrative commands withforce_privateenforcement. - Commands reload dynamically when the bot restarts or modules refresh, automatically discovering
Serviceobjects and their decorated handlers.
Frequently Asked Questions
What is the difference between sv.on_command and sucmd?
sv.on_command registers commands under a specific Service instance with group-based enable/disable logic, while sucmd is a convenience decorator in hoshino/service.py specifically for superuser commands that automatically applies permission=priv.SUPERUSER. The sucmd helper bypasses group service checks and is designed for administrative functions requiring elevated privileges.
How does the decorator handle disabled services?
When a user invokes a command in a group where the service is disabled, the wrapper checks the service state before executing the handler. If disabled, it sends the deny_tip message (if configured) or remains silent, preventing the underlying handler from executing. This logic is implemented in the wrapper function within lines 86-116 of hoshino/service.py.
Can I use NoneBot's native parameters with sv.on_command?
Yes. The on_command decorator accepts and forwards additional keyword arguments to NoneBot's native command registration. Parameters like priority, permission, and block pass through directly to nonebot.on_command, allowing fine-grained control over command matching behavior while retaining HoshinoBot's service management layer.
Where should I place my custom command files?
Place custom modules in the hoshino/modules/ directory structure, typically organized by functionality (e.g., hoshino/modules/example/ping.py). HoshinoBot automatically discovers Python files in these directories during startup, importing them to register Service instances and their decorated command handlers with the running bot instance.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →