简体中文 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
    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 并行

适配器有两个并行的 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 平台错误段的低三位自定义):

错误码 错误名 说明
34001 Request Not Found 请求不存在或已过期
34002 Request Already Handled 请求已被处理
34003 Request Not Supported 平台不支持该类型的请求操作
34004 Permission Denied Bot 无权处理此请求(平台返回)

与框架码的边界:以上 340xx 是平台/适配器返回的请求处理失败; ErisPulse 框架在 scope.actions 禁用某模块的 request 动作时,在调用适配器之前 直接返回 34601(Action Denied,见 API 响应标准 §5.3), 两者互不替代:先过 34601 框架闸口,再落到平台层 340xx 错误。

8. 相关文档