# How to Implement Arena Query Functionality in HoshinoBot: Complete API Integration Guide

> Integrate HoshinoBot arena query functionality with the pcrdfans API. Learn about command handlers, business logic, and data persistence in this comprehensive guide.

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

---

**The arena query feature in HoshinoBot integrates with the pcrdfans API to fetch Princess Connect! Re:Dive attack team recommendations, utilizing a three-layer architecture spanning command handlers in [`__init__.py`](https://github.com/ice9coffee/hoshinobot/blob/main/__init__.py), asynchronous business logic in [`arena.py`](https://github.com/ice9coffee/hoshinobot/blob/main/arena.py), and local JSON persistence for user feedback.**

The **arena query** module in the `ice9coffee/hoshinobot` repository provides a production-ready example of external API integration for gaming bots. This guide explains how to implement arena query functionality in HoshinoBot, covering the complete flow from user command input through rate limiting and image-based response rendering.

## Architecture Overview

The implementation organizes functionality across three distinct layers to separate concerns and maintain maintainability:

**Command and Service Layer** ([`hoshino/modules/priconne/arena/__init__.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/modules/priconne/arena/__init__.py)): Registers bot commands, validates user input, enforces rate limits, and formats replies using image generation utilities.

**Business and Query Layer** ([`hoshino/modules/priconne/arena/arena.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/modules/priconne/arena/arena.py)): Constructs authenticated API requests to the external *pcrdfans* service, parses JSON responses, generates **quick-keys** for result identification, and enriches data with local user feedback.

**Persistence Layer** ([`arena.py`](https://github.com/ice9coffee/hoshinobot/blob/main/arena.py)): Maintains per-user likes and dislikes in a local JSON file located at `~/.hoshino/arena_db.json`, enabling persistent feedback across bot restarts.

## Command Registration and Service Layer

The module initializes a **Service** object to register commands and manage bot lifecycle events. Four distinct command prefixes handle region-specific queries for Princess Connect! Re:Dive servers:

- `怎么拆` (Mainland CN, region=1)
- `b怎么拆` (Bilibili CN, region=2)
- `台怎么拆` (Taiwan, region=3)
- `日怎么拆` (Japan, region=4)

Each prefix maps to the core `_arena_query` function with the appropriate region code. According to lines 38-52 of [`hoshino/modules/priconne/arena/__init__.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/modules/priconne/arena/__init__.py), the registration pattern uses decorators:

```python
@sv.on_prefix(aliases)        # CN

async def arena_query(bot, ev):
    await _arena_query(bot, ev, region=1)

@sv.on_prefix(aliases_b)      # B-CN

async def arena_query_b(bot, ev):
    await _arena_query(bot, ev, region=2)

```

The `region` integer is forwarded directly to the API, allowing the external service to filter results by server without requiring additional logic changes in the bot code.

## Input Validation and Rate Limiting

The `_arena_query` function implements defensive programming to prevent abuse and ensure data quality. First, it instantiates a **FreqLimiter** configured to a 5-second cooldown per user:

```python
if not lmt.check(uid):
    await bot.finish(ev, '您查询得过于频繁，请稍等片段', at_sender=True)
lmt.start_cd(uid)

```

After passing the rate limit, the function extracts plain text from the message event, strips punctuation using `filt_message`, and converts character names or IDs into standardized integer IDs using the shared `chara` utilities. Validation steps include verifying team length constraints, checking for duplicate characters, and filtering out invalid NPC identifiers before proceeding to the API call (lines 81-107 of [`__init__.py`](https://github.com/ice9coffee/hoshinobot/blob/main/__init__.py)).

## API Integration and Business Logic

The core API interaction resides in the `do_query` function within [`arena.py`](https://github.com/ice9coffee/hoshinobot/blob/main/arena.py). This implementation demonstrates robust patterns for authenticated external service communication.

### Authentication and Payload Construction

The module retrieves the **AUTH_KEY** from the global configuration at `config.priconne.arena.AUTH_KEY`, which operators must define in [`hoshino/config/priconne.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/config/priconne.py) (copied from [`hoshino/config_example/priconne.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/config_example/priconne.py)). The authentication header and request payload follow the *pcrdfans* API specification:

```python
def __get_auth_key():
    return config.priconne.arena.AUTH_KEY

# In do_query:

payload = {
    "_sign": "a",
    "def": [id * 100 + 1 for id in def_team_ids],  # API-specific encoding

    "nonce": "a",
    "page": 1,
    "sort": 1,
    "ts": int(time.time()),
    "region": region,
}

```

The defense team IDs are multiplied by 100 and incremented by 1 to match the API's expected format.

### Asynchronous Request Handling

The module uses `aiorequests.post` (an `aiohttp` wrapper defined in [`hoshino/aiorequests.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/aiorequests.py)) to send non-blocking HTTP requests with a 10-second timeout:

```python
resp = await aiorequests.post(
    "https://api.pcrdfans.com/x/v1/search",
    headers={"Authorization": __get_auth_key()},
    json=payload,
    timeout=10,
)

```

This async pattern ensures the bot remains responsive to other events while awaiting the external API response.

### Response Parsing and Quick-Key Generation

Upon receiving the response, the code verifies the status code (`res["code"] == 0`) before processing. For each attack team recommendation, the system:

1. Generates a **quick-key** via `gen_quick_key` — a 5-character base-32 string that maps to the true arena solution ID
2. Converts raw character IDs into `chara` objects using `chara.fromid` for image rendering
3. Aggregates like/dislike counts from the local [`arena_db.json`](https://github.com/ice9coffee/hoshinobot/blob/main/arena_db.json) database

The quick-key system (lines 93-100 of [`arena.py`](https://github.com/ice9coffee/hoshinobot/blob/main/arena.py)) enables users to reference specific attack teams using short codes (e.g., `AB3DE`) without exposing internal database identifiers.

## Rendering Results and Handling Feedback

After retrieving up to six results, `_arena_query` passes the data to `render_atk_def_teams`, which generates a composite image showing defense teams, recommended attacks, vote counts, and quick-keys. The image is base-64 encoded using `pic2b64` and transmitted via `MessageSegment.image` (lines 35-44 of [`__init__.py`](https://github.com/ice9coffee/hoshinobot/blob/main/__init__.py)).

Users provide feedback using the commands `点赞+<quick-key>` (like) or `点踩+<quick-key>` (dislike). The `_arena_feedback` handler validates the 5-character key, resolves it to the true ID using `arena.get_true_id`, updates the in-memory database via `add_like` or `add_dislike`, and persists changes immediately with `dump_db` to `~/.hoshino/arena_db.json` (lines 86-98 of [`__init__.py`](https://github.com/ice9coffee/hoshinobot/blob/main/__init__.py)).

## Configuration Requirements

To activate the arena query functionality, operators must configure the authentication key:

1. Copy [`hoshino/config_example/priconne.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/config_example/priconne.py) to [`hoshino/config/priconne.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/config/priconne.py)
2. Replace the placeholder in the **arena** class:

```python
class arena:
    AUTH_KEY = "your_pcrdfans_api_key_here"

```

Obtain the API key from the *pcrdfans* developer portal. Without this configuration, the `do_query` function will fail to authenticate with the external service.

## Extending the Implementation

You can adapt this module for different data sources or additional game servers.

### Integrating a Custom API Endpoint

To point the bot at a different arena database, modify the URL constant in [`hoshino/modules/priconne/arena/arena.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/modules/priconne/arena/arena.py):

```python

# Replace the default pcrdfans endpoint

API_URL = "https://my.custom.service/arena/search"

resp = await aiorequests.post(API_URL, headers=header, json=payload, timeout=10)

```

The payload structure remains compatible with any REST endpoint accepting the same JSON schema.

### Adding Support for New Regions

To support a new region (e.g., Korea as region=5), extend the command aliases in [`__init__.py`](https://github.com/ice9coffee/hoshinobot/blob/main/__init__.py):

```python
aliases_kr = ('韩怎么拆', 'kr怎么拆')

@sv.on_prefix(aliases_kr)
async def arena_query_kr(bot, ev):
    await _arena_query(bot, ev, region=5)

```

No modifications to `arena.do_query` are required, as the region parameter passes through transparently to the API payload.

## Summary

- The arena module implements a clean separation between command handling ([`__init__.py`](https://github.com/ice9coffee/hoshinobot/blob/main/__init__.py)), API business logic ([`arena.py`](https://github.com/ice9coffee/hoshinobot/blob/main/arena.py)), and JSON persistence.
- **FreqLimiter** enforces 5-second cooldowns per user to prevent API spam, while **quick-keys** provide a compact addressing scheme for user feedback.
- The `do_query` function constructs authenticated POST requests to `https://api.pcrdfans.com/x/v1/search` using `aiorequests.post`, handling async I/O without blocking the bot event loop.
- Configuration requires only the `AUTH_KEY` in [`hoshino/config/priconne.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/config/priconne.py), making deployment straightforward for operators with valid API credentials.

## Frequently Asked Questions

### How do I obtain the API key required for the arena query?

Register at the *pcrdfans* developer portal to receive an authentication key. Place this key in [`hoshino/config/priconne.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/config/priconne.py) within the `arena.AUTH_KEY` variable, following the structure shown in [`hoshino/config_example/priconne.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/config_example/priconne.py). The bot loads this configuration at startup to authenticate all API requests.

### Can I replace the pcrdfans API with a custom arena database?

Yes. Modify the URL string in the `do_query` function within [`hoshino/modules/priconne/arena/arena.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/modules/priconne/arena/arena.py). The module uses standard `aiorequests.post` with a JSON payload containing defense teams, region codes, and timestamps, making it compatible with any REST endpoint implementing a similar search interface. Ensure your custom API returns responses in the expected format with `code`, `data`, and result entries.

### What is the purpose of the quick-key system?

The **quick-key** is a 5-character base-32 string generated by `gen_quick_key` that acts as a short alias for the true arena solution ID. This abstraction allows users to reference specific attack team recommendations using compact codes like `AB3DE` when issuing `点赞` (like) or `点踩` (dislike) commands. The mapping is maintained in memory during runtime and resolved via `get_true_id` when processing feedback.

### How do I add support for additional server regions?

Define new command alias tuples in [`hoshino/modules/priconne/arena/__init__.py`](https://github.com/ice9coffee/hoshinobot/blob/main/hoshino/modules/priconne/arena/__init__.py) and bind them to `@sv.on_prefix` decorators that invoke `_arena_query` with the appropriate region integer (e.g., `region=5` for a new server). The `do_query` function forwards this region code directly to the API payload without requiring changes to the core query logic, provided the external service supports the new region identifier.