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

HoshinoBot uses two configuration variables, RES_DIR and RES_PROTOCOL, located in 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:

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, which expands user directories and asserts protocol compatibility:


# 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, 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:


# 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:

    cp -r hoshino/config_example hoshino/config
  3. Edit hoshino/config/__bot__.py to set your protocol and directory:

    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:

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:

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:

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:


# 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:


# 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 and validated in __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 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.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →