# How to Implement Custom Bot Commands Using the `on_command` Decorator in HoshinoBot

> Learn to implement custom bot commands in HoshinoBot using the on_command decorator. Seamlessly add commands with built-in checks and NoneBot integration for your bot.

- Repository: [ice9coffee/hoshinobot](https://github.com/ice9coffee/hoshinobot)
- Tags: how-to-guide
- Published: 2026-03-03

---

**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`](https://github.com/ice9coffee/hoshinobot/blob/main/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`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/service.py), the `Service` class initializes with metadata that controls visibility and default behavior:

```python
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`](https://github.com/ice9coffee/hoshinobot/blob/main/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`](https://github.com/ice9coffee/hoshinobot/blob/main/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:

1. **Group verification** – Confirms the message originates from a group chat
2. **Service state check** – Verifies the service is enabled for the specific group
3. **Mention validation** – Respects the `only_to_me` flag when present
4. **Logging** – Records execution success or failure for debugging

To implement a command, decorate an async function that accepts a `CommandSession` parameter:

```python
@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`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/modules/example/ping.py):

```python
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`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/service.py):

```python
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:

```python
@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_command` decorator** (lines 86-116 in [`hoshino/service.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/service.py)) automatically handles group validation, service enable checks, and NoneBot registration.
- Use **`aliases`** to define command shortcuts, **`only_to_me`** to restrict mentions, and **`deny_tip`** to customize disable notifications.
- The **`sucmd` helper** provides shorthand syntax for superuser-only administrative commands with `force_private` enforcement.
- Commands reload dynamically when the bot restarts or modules refresh, automatically discovering `Service` objects 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`](https://github.com/ice9coffee/hoshinobot/blob/main/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`](https://github.com/ice9coffee/hoshinobot/blob/main/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`](https://github.com/ice9coffee/hoshinobot/blob/main/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.