# How HoshinoBot's Service System Works: A Complete Guide to Creating Custom Services

> Discover how HoshinoBot's service system isolates functionality into modular objects. Learn to create custom HoshinoBot services for command registration, permissions, and config management easily.

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

---

**HoshinoBot isolates functionality into modular Service objects that handle command registration, permission checks, and configuration management automatically.**

The HoshinoBot service system provides a structured framework for organizing bot functionality into discrete, manageable units. In the `ice9coffee/hoshinobot` repository, each Service instance acts as a self-contained module that bundles related commands, triggers, and scheduled jobs while handling permissions and group-specific enable states internally.

## Core Architecture of the HoshinoBot Service System

### The Service Class ([`hoshino/service.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/service.py))

At the heart of the system lies the `Service` class defined in [`hoshino/service.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/service.py). When instantiated, a Service automatically registers itself in the global `_loaded_services` dictionary and optionally joins a service bundle for organizational grouping.

The constructor signature reveals the configuration options available:

```python
class Service:
    def __init__(self, name, use_priv=None, manage_priv=None,
                 enable_on_default=None, visible=None, help_=None, bundle=None):
        # Registers service, loads JSON config from ~/.hoshino/service_config/

        # Sets permission levels and metadata...

```

Each Service maintains its own configuration JSON stored in `~/.hoshino/service_config/{service_name}.json`, which persists visibility settings, default enable states, and group-specific allow/deny lists across bot restarts.

### Configuration and State Management

The Service system handles complex state management automatically. When loading, each Service instance:

- Retrieves its persisted configuration from the JSON store
- Applies default permission levels (`use_priv` for command execution, `manage_priv` for administrative control)
- Tracks which groups have explicitly enabled or disabled the service
- Maintains visibility flags for service listing commands

This architecture allows group administrators to toggle services on or off per-group without modifying code, with all changes persisting to disk automatically.

## How Triggers Work in HoshinoBot

### Trigger Registration via Decorators

The HoshinoBot service system provides a comprehensive set of decorator methods for registering message handlers. These decorators abstract the complexity of trigger matching while providing clean, readable syntax for command definition.

Available decorators in [`hoshino/service.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/service.py) include:

- `on_message` - Raw message handling
- `on_prefix` - Commands starting with specific strings
- `on_fullmatch` - Exact message matching
- `on_suffix` - Commands ending with specific strings
- `on_keyword` - Substring matching
- `on_rex` - Regular expression matching
- `on_command` - Structured command handling
- `on_natural_language` - Natural language processing

Each decorator creates a `ServiceFunc` wrapper that maintains references to both the service instance and the original handler function. For example, the `on_prefix` implementation:

```python
def on_prefix(self, *prefix, only_to_me=False):
    def deco(func):
        sf = ServiceFunc(self, func, only_to_me)
        for p in prefix:
            trigger.prefix.add(p, sf)  # Registers with PrefixTrigger

        return func
    return deco

```

### Trigger Matching and Execution ([`hoshino/trigger.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/trigger.py))

The actual message matching logic resides in [`hoshino/trigger.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/trigger.py), which defines concrete trigger classes including `PrefixTrigger`, `SuffixTrigger`, `KeywordTrigger`, and `RexTrigger`.

These triggers use efficient data structures for matching:

- Prefix triggers utilize trie structures for longest-prefix matching
- Keyword triggers scan for substring occurrences
- Regex triggers compile patterns for efficient matching

When a message arrives, the `PrefixTrigger.find_handler` method extracts the relevant text slice, performs the lookup, and yields the associated `ServiceFunc` objects for execution.

## Permission and Enable Checks

### The _check_all Method

Before executing any handler, the HoshinoBot service system validates permissions through the `_check_all` method in [`hoshino/service.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/service.py). This centralized check ensures consistent security enforcement across all services.

The validation chain includes:

1. Service enablement check for the specific group (`check_enabled`)
2. Group blocklist verification (`priv.check_block_group`)
3. User permission level validation against `use_priv` (`priv.check_priv`)

```python
def _check_all(self, ev):
    gid = ev.group_id
    return self.check_enabled(gid) and not priv.check_block_group(gid) \
           and priv.check_priv(ev, self.use_priv)

```

This architecture allows fine-grained control where services can be restricted to specific groups or require elevated privileges without implementing custom permission logic in each handler.

### Service Management Commands

Administrative control of services is handled through [`hoshino/modules/botmanage/service_manage.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/modules/botmanage/service_manage.py), which provides commands for runtime service manipulation.

Group administrators can use commands like:

- `enable <service>` - Activate a service for the current group
- `disable <service>` - Deactivate a service for the current group
- `ls` or `service list` - Display available services

These commands interact directly with the Service instances to modify their enable states and persist changes to the JSON configuration files.

## Creating Custom Services in HoshinoBot

### Minimal Service Example

Creating a custom HoshinoBot service requires only instantiating a `Service` object and attaching handlers using decorators. The minimal implementation demonstrates the framework's simplicity:

```python

# my_service.py

from hoshino import Service

# Create service with unique name and help text

sv = Service('hello', help_='Simple greeting service', use_priv=0)

@sv.on_prefix('hi')
async def greet(bot, ev):
    await bot.send(ev, 'Hello! 👋')

```

Place this file in `hoshino/modules/` or any importable path within the module directory structure. The bot automatically discovers and loads the service on startup, registering the prefix trigger without additional configuration.

### Command-Style Service with Arguments

For services requiring structured arguments and complex logic, the `on_command` decorator provides session-based argument parsing:

```python

# weather.py

import aiohttp
from hoshino import Service

sv = Service('weather', help_='Weather lookup service', visible=True)

@sv.on_command('weather', aliases=('w', '天气'))
async def weather_cmd(session):
    city = session.current_arg_text.strip()
    
    if not city:
        await session.send('Usage: !weather <city>', at_sender=True)
        return
    
    async with aiohttp.ClientSession() as s:
        async with s.get(f'https://api.example.com/weather?q={city}') as r:
            data = await r.json()
    
    await session.send(f'{city}: {data["temp"]}°C, {data["condition"]}')

```

This example demonstrates alias support, argument validation, and external API integration while maintaining the service's permission and enablement checks automatically.

### Scheduled Jobs and Cron Tasks

Services can define periodic tasks using the `scheduled_job` decorator, which integrates with the underlying APScheduler:

```python

# reminder.py

from hoshino import Service
import datetime

sv = Service('reminder', help_='Daily reminders', visible=False)

@sv.scheduled_job('cron', hour='9', minute='0')
async def daily_good_morning():
    # Get all groups where this service is enabled

    groups = await sv.get_enable_groups()
    
    for gid, bot_ids in groups.items():
        for bot_id in bot_ids:
            await sv.bot.send_group_msg(
                self_id=bot_id,
                group_id=gid,
                message='Good morning! 🌞'
            )

```

This pattern enables broadcast functionality while respecting per-group service enablement states, ensuring messages only reach groups that have explicitly enabled the reminder service.

## Summary

- **Service Architecture**: HoshinoBot organizes functionality into `Service` objects defined in [`hoshino/service.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/service.py), each managing its own configuration, permissions, and trigger handlers.

- **Trigger System**: Decorators like `on_prefix`, `on_rex`, and `on_command` register handlers with trigger managers in [`hoshino/trigger.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/trigger.py), using efficient matching algorithms for message routing.

- **Permission Model**: The `_check_all` method enforces group-specific enable states and privilege levels (`use_priv`, `manage_priv`) before executing any handler.

- **Custom Development**: Creating new services requires only instantiating a `Service` object and attaching handlers via decorators, with optional scheduled jobs for periodic tasks.

- **Runtime Management**: Administrative commands in [`hoshino/modules/botmanage/service_manage.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/modules/botmanage/service_manage.py) allow group admins to enable, disable, and list services without code changes.

## Frequently Asked Questions

### What is the minimum code required to create a working HoshinoBot service?

You need only import the `Service` class, instantiate it with a unique name, and apply a decorator to a handler function. Place the file in `hoshino/modules/` and restart the bot. The service automatically loads, registers its triggers, and handles permission checks without additional boilerplate.

### How does HoshinoBot determine which service handles an incoming message?

When a message arrives, the trigger managers in [`hoshino/trigger.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/trigger.py) iterate through registered handlers. Each trigger type (prefix, suffix, keyword, regex) uses optimized lookup structures to find matching `ServiceFunc` wrappers. Before execution, the service's `_check_all` method verifies the service is enabled for that group and the user has sufficient privileges.

### Can I restrict a custom service to specific user groups or permission levels?

Yes. When creating a `Service` instance, set the `use_priv` parameter to control who can invoke commands (0 for everyone, 1 for group admins, 2 for bot admins). The `manage_priv` parameter controls who can enable or disable the service. Additionally, group admins can toggle services per-group using the built-in `enable` and `disable` commands.

### Where does HoshinoBot store service configuration and enable states?

Service configurations persist as JSON files in `~/.hoshino/service_config/{service_name}.json`. These files store default enable states, visibility settings, and per-group allow/deny lists. The `Service` class automatically loads these configurations on instantiation and saves changes when administrators modify service states through chat commands.