简体中文 English 繁體中文 日本語 Русский
本文为静态镜像,内容以交互版为准 在交互式文档中心打开 →

ErisPulse リクエスト操作規格

本ドキュメントでは、ErisPulseアダプターにおけるリクエストイベント操作の標準化された規格を定義します。これには、リクエストイベントのフィールド要件、Request DSLの使用方法、アダプター実装要件が含まれます。

1. 概要

リクエストイベント(type: "request")は、OneBot12標準で定義された特殊なイベントタイプで、Botに意思決定を求めるリクエスト(友達申請、グループ招待など)を表します。

メッセージイベントとは異なり、リクエストイベントは双方向のやり取りが必要です:

  1. 受信:アダプターがプラットフォームの原生リクエストを標準リクエストイベントに変換
  2. 応答:モジュールがRequest DSLまたはEvent.approve()/Event.reject()を使って操作を実行
プラットフォーム原生リクエストイベント
    │
    ▼
Converter.convert()        ← アダプター実装(正方向変換)
    │
    ▼
標準リクエストイベント (request_idを含む)
    │
    ├─→ モジュール処理器 @request.on_friend_request()
    │       │
    │       ├─→ event.approve()     ← 申請を承認
    │       └─→ event.reject()      ← 申請を拒否
    │               │
    │               ▼
    │       adapter.Request(request_id).accept()
    │               │
    │               ▼
    │       BaseAdapter.Request.accept()  ← アダプターのオーバーライド
    │               │
    │               ▼
    │       プラットフォームAPI呼び出し
    │
    └─→ または直接アダプター操作で実行
            await adapter.Request("req_id").accept()

2. リクエストイベントのフィールド要件

2.1 標準フィールド

リクエストイベントには、OneBot12標準フィールドに加えて、以下のフィールドを含める必要があります:

フィールド 型 必須 説明
request_id string 強く推奨 操作用のリクエスト識別子
user_id string はい リクエスト発起者のID
user_nickname string いいえ リクエスト発起者のニックネーム
comment string いいえ リクエストの備考

2.2 request_id フィールド

request_idはリクエスト操作の中心となる識別子です:

2.3 リクエストイベントの例

{
  "id": "evt_123456",
  "time": 1752241225,
  "type": "request",
  "detail_type": "friend",
  "platform": "onebot11",
  "self": {
    "platform": "onebot11",
    "user_id": "bot_123"
  },
  "user_id": "user_456",
  "user_nickname": "YingXinche",
  "comment": "友達申請してください",
  "request_id": "flag_abc123",
  "onebot11_raw": {...},
  "onebot11_raw_type": "request"
}

3. Request DSL

3.1 チェーン呼び出し

RequestはSendと同様のチェーン呼び出しインターフェースを提供します:

# 基本的な使い方
await adapter.Request("req_id").accept()
await adapter.Request("req_id").reject()

# Botアカウントを指定
await adapter.Request("req_id").Using("bot1").accept()

# 备考を指定(kwargsで)
await adapter.Request("req_id").accept(comment="ようこそ")
await adapter.Request("req_id").reject(comment="今は追加できません")

# 組み合わせ
await adapter.Request("req_id").Using("bot1").accept(comment="ようこそ")

3.2 メソッド一覧

メソッド 説明 戻り値
Using(account_id) 操作実行用のBotアカウントを指定 RequestDSL(チェーン呼び出し可能)
accept(**kwargs) リクエストを承認 asyncio.Task(await後に標準レスポンスを返す)
reject(**kwargs) リクエストを拒否 asyncio.Task(await後に標準レスポンスを返す)

3.3 戻り値の形式

操作は標準APIレスポンス形式を返します:

成功時:

{
    "status": "ok",
    "retcode": 0,
    "data": null,
    "message_id": "",
    "message": ""
}

失敗時:

{
    "status": "failed",
    "retcode": 34001,
    "data": null,
    "message_id": "",
    "message": "リクエストが期限切れまたは存在しません"
}

未実装時(アダプターがaccept/rejectをオーバーライドしていない場合):

{
    "status": "failed",
    "retcode": 10002,
    "data": null,
    "message_id": "",
    "message": "プラットフォーム MyAdapter はリクエスト操作 (accept) を実装していません"
}

4. Event 便利メソッド

Eventラッパークラスには、リクエストイベントハンドラで使用する便利メソッドが用意されています:

from ErisPulse.Core.Event import request

@request.on_friend_request()
async def handle_friend_request(event):
    # リクエストIDを取得
    request_id = event.get_request_id()
    if not request_id:
        print("警告:リクエストイベントに request_id がありません")
        return
    
    # リクエストを承認
    result = await event.approve()
    
    # またはリクエストを拒否
    # result = await event.reject(comment="今は友達追加できません")
    
    # 結果を確認
    if result.get("status") == "ok":
        print("操作成功")
    else:
        print(f"操作失敗: {result.get('message')}")

4.1 Event メソッド一覧

メソッド 説明 戻り値
get_request_id() リクエストIDを取得 str
approve(comment=None) 現在のリクエストイベントを承認 標準レスポンス形式
reject(comment=None) 現在のリクエストイベントを拒否 標準レスポンス形式

5. アダプター実装要件

5.1 転換器要件

アダプターの転換器は、リクエストイベントを転換する際に、必ずrequest_idフィールドを正しく設定する必要があります:

def convert_request_event(self, raw_event: dict) -> dict:
    """プラットフォーム原生リクエストイベントを転換"""
    return {
        "id": self._generate_event_id(raw_event),
        "time": int(time.time()),
        "type": "request",
        "detail_type": self._map_request_type(raw_event),  # "friend" または "group"
        "platform": self._platform_name,
        "self": {
            "platform": self._platform_name,
            "user_id": str(self._bot_id),
        },
        "user_id": str(raw_event.get("user_id", "")),
        "user_nickname": raw_event.get("nickname", ""),
        "comment": raw_event.get("message", ""),
        "request_id": self._extract_request_id(raw_event),  # ← 重要なフィールド
        f"{self._platform_name}_raw": raw_event,
        f"{self._platform_name}_raw_type": raw_event.get("type", ""),
    }

def _extract_request_id(self, raw_event: dict) -> str:
    """
    プラットフォーム原生イベントからリクエストIDを抽出
    
    まずプラットフォームの原生IDを使用し、ない場合はユニークIDを生成
    """
    # まずプラットフォームの原生IDを使用
    if flag := raw_event.get("flag"):
        return str(flag)
    if request_key := raw_event.get("request_key"):
        return str(request_key)
    
    # デフォルト:ユニークIDを生成
    import hashlib
    raw = f"{self._platform_name}_{raw_event.get('user_id')}_{raw_event.get('timestamp')}"
    return hashlib.md5(raw.encode()).hexdigest()

5.2 Request 内部クラス実装

アダプターはRequest内部クラスでacceptとrejectをオーバーライドするだけで実装できます:

from ErisPulse.Core import BaseAdapter, RequestDSL

class MyAdapter(BaseAdapter):
    
    class Request(RequestDSL):
        """MyPlatform リクエスト操作実装"""
        
        def accept(self, **kwargs):
            """
            リクエストを承認
            
            :param kwargs: 扩張パラメータ、comment="備考"など
            :return: asyncio.Task
            """
            async def _do():
                try:
                    result = await self._adapter.call_api(
                        endpoint="/set_request",
                        request_id=self._request_id,
                        approve=True,
                        **kwargs,
                    )
                    return {
                        "status": "ok" if result.get("code") == 0 else "failed",
                        "retcode": result.get("code", 0),
                        "data": None,
                        "message_id": "",
                        "message": result.get("message", ""),
                    }
                except Exception as e:
                    return {
                        "status": "failed",
                        "retcode": 34001,
                        "data": None,
                        "message_id": "",
                        "message": f"リクエスト操作失敗: {e}",
                    }
            
            return self._create_task(_do())
        
        def reject(self, **kwargs):
            """リクエストを拒否"""
            async def _do():
                try:
                    result = await self._adapter.call_api(
                        endpoint="/set_request",
                        request_id=self._request_id,
                        approve=False,
                        **kwargs,
                    )
                    return {
                        "status": "ok" if result.get("code") == 0 else "failed",
                        "retcode": result.get("code", 0),
                        "data": None,
                        "message_id": "",
                        "message": result.get("message", ""),
                    }
                except Exception as e:
                    return {
                        "status": "failed",
                        "retcode": 34001,
                        "data": None,
                        "message_id": "",
                        "message": f"リクエスト操作失敗: {e}",
                    }
            
            return self._create_task(_do())

5.3 プラットフォームがリクエスト操作をサポートしていない場合

プラットフォームが友達申請/グループ招待操作をサポートしていない場合(一部のプラットフォームでは申請を自動処理する場合)、アダプターは以下のように対応できます:

  1. Request内部クラスをオーバーライドしない:基底クラスのデフォルト実装を使用し、accept()/reject()を呼び出すとretcode=10002を返す
  2. request_idを生成しない:request_idを生成せず、event.approve()がValueErrorを送出するようにする
  3. ログを記録する:accept/rejectで警告を記録し、適切なエラーコードを返す

5.4 まとめ:Send と Request は並列

アダプターには、それぞれ異なる役割を持つ2つのDSL内部クラスがあります:

BaseAdapter
├── Send(SendDSL)     ← メッセージ送信
│   ├── Raw_ob12()    ← 必須実装
│   ├── Text()        ← 推奨実装
│   └── Image()       ← 必要に応じて実装
│
└── Request(RequestDSL) ← リクエスト操作
    ├── accept()        ← 必要に応じて実装
    └── reject()        ← 必要に応じて実装

5.5 アダプター __init__ の注意事項

Request内部クラスの__init__をオーバーライドする際は、引数を透過的に渡し、super().__init__()を呼び出す必要があります。詳しくはアダプター開発入門 - __init__の注意事項(Requestも同様、引数はadapter, request_id, account_id)をご覧ください。

6. アダプター実装チェックリスト

基本要件

リクエストイベントの転換

リクエスト操作

7. エラーコードの拡張

リクエスト操作に関連するアダプター実装層の推奨エラーコード(APIレスポンス標準 §3.2に従い、34xxxプラットフォームエラーセグメントの下3桁を独自に定義):

エラーコード エラーネーム 説明
34001 Request Not Found リクエストが存在しない、または期限切れ
34002 Request Already Handled リクエストは既に処理済み
34003 Request Not Supported プラットフォームがこのタイプのリクエスト操作をサポートしていない
34004 Permission Denied Botがこのリクエストを処理する権限がない(プラットフォームが返す)

フレームワークコードとの境界:上記の340xxはプラットフォーム/アダプターが返すリクエスト処理失敗です。ErisPulseフレームワークがscope.actionsで特定のモジュールのリクエスト操作を禁止した場合、アダプターを呼び出す前に直接34601(Action Denied、APIレスポンス標準 §5.3)を返します。この2つは互いに補完するものであり、まず34601フレームワークのチェックを通過し、その後プラットフォーム層の340xxエラーに到達します。

8. 関連ドキュメント