# How to Configure and Use the Resource File System (RES_DIR, RES_PROTOCOL) in HoshinoBot

> Learn to configure and use HoshinoBot's resource file system RES_DIR and RES_PROTOCOL. Control static asset storage and delivery for OneBot/CQHTTP clients.

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

---

**HoshinoBot uses two configuration variables, `RES_DIR` and `RES_PROTOCOL`, located in [`hoshino/config/__bot__.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/config/__bot__.py), to control where static assets are stored and how they are delivered to OneBot/CQHTTP clients as either file paths, HTTP URLs, or Base64 data URIs.**

The resource file system in **HoshinoBot** (a Python-based QQ bot framework) provides a secure, protocol-agnostic way to manage static assets like images, JSON data, and audio files. By configuring `RES_DIR` and `RES_PROTOCOL`, you control both the physical storage location and the transport method used when sending files to chat clients.

## Understanding RES_DIR and RES_PROTOCOL Configuration

HoshinoBot defines resource behavior through three primary configuration variables in **[`hoshino/config_example/__bot__.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/config_example/__bot__.py)**:

| Variable | Purpose | Valid Values |
|----------|---------|--------------|
| `RES_DIR` | Absolute or relative path to the resource root directory | Any filesystem path (e.g., `./res/`, `~/bot_assets/`) |
| `RES_PROTOCOL` | Transport protocol for delivering assets to clients | `"file"` (default), `"http"`, `"base64"` |
| `RES_URL` | Base HTTP URL for assets (required only when `RES_PROTOCOL == "http"`) | Valid URL ending with `/` (e.g., `http://127.0.0.1:5000/static/`) |

The configuration validation occurs in **[`hoshino/config_example/__init__.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/config_example/__init__.py)**, which expands user directories and asserts protocol compatibility:

```python

# hoshino/config_example/__init__.py

RES_DIR = os.path.expanduser(RES_DIR)
assert RES_PROTOCOL in ('http', 'file', 'base64')

```

To activate your configuration, rename the `config_example` directory to `config` so HoshinoBot loads these values at startup.

## How the Resource System Works Internally

The core implementation resides in **[`hoshino/R.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/R.py)**, which provides two wrapper classes for resource management:

- **`ResObj`** – Generic file representation exposing a `path` property (absolute filesystem path) and optional `url` property (for HTTP protocol)
- **`ResImg`** – Specialized subclass that converts resources into CQ HTTP message segments via the `cqcode` property

When you call `R.img()`, the system constructs a `ResImg` instance and generates the appropriate CQ code based on your `RES_PROTOCOL`:

```python

# hoshino/R.py (simplified logic)

class ResImg(ResObj):
    @property
    def cqcode(self) -> MessageSegment:
        if hoshino.config.RES_PROTOCOL == 'http':
            return MessageSegment.image(self.url)
        elif hoshino.config.RES_PROTOCOL == 'file':
            return MessageSegment.image(f'file:///{os.path.abspath(self.path)}')
        else:  # base64

            return MessageSegment.image(util.pic2b64(self.open()))

```

The module exposes two factory functions that automatically prepend `RES_DIR` and validate path safety:

- `R.get(*paths)` – Returns a `ResObj` for arbitrary file types
- `R.img(*paths)` – Returns a `ResImg` ready for chat transmission

Both functions enforce directory containment; attempting to traverse above `RES_DIR` raises a `ValueError`.

## Step-by-Step Configuration Guide

Follow these steps to configure the resource file system for your deployment:

1. **Create the resource directory** at your preferred location (e.g., `./res/` or `/opt/hoshino/res/`).

2. **Copy and rename the configuration folder**:
   ```bash
   cp -r hoshino/config_example hoshino/config
   ```

3. **Edit [`hoshino/config/__bot__.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/config/__bot__.py)** to set your protocol and directory:
   ```python
   RES_PROTOCOL = 'file'  # Options: file | http | base64

   RES_DIR = r'./res/'    # Use raw strings for Windows paths

   RES_URL = 'http://127.0.0.1:5000/static/'  # Only for http protocol

   ```

4. **Restart HoshinoBot** to load the updated configuration.

5. **Verify access** by placing a test image in your `RES_DIR` and using the code examples below.

## Practical Usage Examples

### Sending Images with R.img()

The most common use case involves sending images stored in your resource directory:

```python
from hoshino import R

# Sends ./res/img/pikachu.png using the configured RES_PROTOCOL

await bot.send(ev, R.img('img/pikachu.png').cqcode)

```

### Combining Text and Resources

You can embed resource references within formatted messages:

```python
msg = f"Boss Map: {R.img('raid/boss_map.jpg').cqcode}\nGood luck!"
await bot.send(ev, msg)

```

### Reading Non-Image Files

For JSON configurations or text data, use `R.get()` to obtain the absolute filesystem path:

```python
from hoshino import R
import json

config_path = R.get('data', 'gacha_config.json').path  # Resolves to ./res/data/gacha_config.json

with open(config_path, encoding='utf-8') as f:
    rates = json.load(f)

```

### Serving Resources via HTTP

When running the bot and CQHTTP client on different machines, configure the HTTP protocol:

```python

# hoshino/config/__bot__.py

RES_PROTOCOL = 'http'
RES_URL = 'http://192.168.1.100:8080/res/'

```

Ensure your web server (nginx, Flask, or FastAPI) serves the `RES_DIR` directory at the specified `RES_URL` path. HoshinoBot will then transmit full URLs instead of local file paths.

### Embedding Base64 Data URIs

For standalone deployments without a file server, use Base64 encoding:

```python

# hoshino/config/__bot__.py

RES_PROTOCOL = 'base64'

```

This setting causes `R.img().cqcode` to encode images as Base64 data URIs using `util.pic2b64()`, eliminating external HTTP dependencies at the cost of increased message payload size.

## Summary

- **Configuration files** are located in [`hoshino/config_example/__bot__.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/config_example/__bot__.py) and validated in [`__init__.py`](https://github.com/ice9coffee/hoshinobot/blob/main/__init__.py)
- **Three protocols** are supported: `file` (local filesystem), `http` (remote URL), and `base64` (embedded data)
- **Core classes** `ResObj` and `ResImg` in [`hoshino/R.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/R.py) handle path resolution and CQ code generation
- **Factory methods** `R.get()` and `R.img()` provide safe, validated access to resources while preventing directory traversal
- **Protocol switching** requires only changing `RES_PROTOCOL` and optionally `RES_URL`, with no code changes needed in modules

## Frequently Asked Questions

### What is the difference between RES_PROTOCOL file and base64?

**`file`** sends a `file://` URI referencing the local filesystem, requiring the bot and QQ client to run on the same machine with shared storage access. **`base64`** embeds the entire image as a data URI, Works across network boundaries but increases network traffic by approximately 33% due to encoding overhead.

### How do I configure RES_PROTOCOL http for remote servers?

Set `RES_PROTOCOL = 'http'` and define `RES_URL` as the externally accessible URL serving your `RES_DIR` directory. Deploy an HTTP server (such as nginx or Python's `http.server`) to host the resource folder at that URL, ensuring the path structure matches your local `RES_DIR` layout.

### Why does R.img raise a ValueError?

HoshinoBot validates all resource paths to prevent directory traversal attacks. A `ValueError` indicates your path attempted to escape the `RES_DIR` root (e.g., using `../` sequences). Always use relative paths within the resource directory, such as `R.img('subfolder/image.jpg')`.

### Where should I place resource files for a plugin?

Create a subdirectory under `RES_DIR` (e.g., `./res/my_plugin/`) and reference it using `R.img('my_plugin/asset.png')`. This keeps plugin assets organized and maintains compatibility with all three transport protocols without hardcoding absolute paths in your source code.