# 统一事件体系

## 1. 设计原则

- **命名空间分类**：事件类型采用 `{namespace}.{action}` 格式，如 `message.received`
- **通用优先**：大部分平台都支持的事件抽象为通用事件，定义统一的字段格式
- **平台特有事件标准化**：各适配器的独有事件通过 `PlatformSpecificEvent` 承载，保留原始数据
- **向后兼容**：现有 `FriendMessage` / `GroupMessage` 通过兼容层映射到新的 `message.received` 事件

## 2. 事件基类层次

```
Event (事件基类)
├── MessageEvent (消息相关事件)
│   ├── MessageReceivedEvent       # message.received
│   ├── MessageEditedEvent         # message.edited
│   ├── MessageDeletedEvent        # message.deleted
│   └── MessageReactionEvent       # message.reaction
├── FeedbackEvent (用户反馈事件)
│   └── FeedbackReceivedEvent      # feedback.received
├── GroupEvent (群组相关事件)
│   ├── MemberJoinedEvent          # group.member_joined
│   ├── MemberLeftEvent            # group.member_left
│   ├── MemberBannedEvent          # group.member_banned
│   ├── MemberUnbannedEvent        # group.member_unbanned
│   └── GroupInfoUpdatedEvent      # group.info_updated
├── FriendEvent (好友相关事件)
│   ├── FriendRequestReceivedEvent # friend.request_received
│   ├── FriendAddedEvent           # friend.added
│   └── FriendRemovedEvent         # friend.removed
├── BotEvent (Bot 状态事件)
│   ├── BotInvitedToGroupEvent     # bot.invited_to_group
│   ├── BotRemovedFromGroupEvent   # bot.removed_from_group
│   ├── BotMutedEvent              # bot.muted
│   └── BotUnmutedEvent            # bot.unmuted
└── PlatformSpecificEvent          # platform.{adapter}.{action}
```

## 3. 通用事件定义

### 3.1 事件基类

```python
class Event(pydantic.BaseModel):
    """事件基类"""

    type: str
    """事件类型标识，如 'message.received'"""

    timestamp: float
    """事件发生的时间戳"""

    bot_uuid: str
    """接收到此事件的 Bot UUID"""

    adapter_name: str
    """产生此事件的适配器名称"""

    source_platform_object: typing.Optional[typing.Any] = None
    """原始平台事件对象，供适配器内部使用"""
```

### 3.2 消息事件

#### MessageReceivedEvent (`message.received`)

收到新消息。这是最核心的事件，替代现有的 `FriendMessage` / `GroupMessage`。

```python
class MessageReceivedEvent(Event):
    """收到新消息"""

    type: str = "message.received"

    message_id: typing.Union[int, str]
    """消息 ID"""

    message_chain: MessageChain
    """消息内容"""

    sender: User
    """发送者"""

    chat_type: ChatType  # "private" | "group"
    """会话类型"""

    chat_id: typing.Union[int, str]
    """会话 ID（私聊为对方用户 ID，群聊为群 ID）"""

    group: typing.Optional[Group] = None
    """群信息（仅群聊时存在）"""
```

与现有类型的映射关系：
- `chat_type == "private"` → 等价于现有 `FriendMessage`
- `chat_type == "group"` → 等价于现有 `GroupMessage`

`ChatType` 枚举：

```python
class ChatType(str, Enum):
    PRIVATE = "private"
    GROUP = "group"
```

#### MessageEditedEvent (`message.edited`)

消息被编辑。

```python
class MessageEditedEvent(Event):
    """消息被编辑"""

    type: str = "message.edited"

    message_id: typing.Union[int, str]
    """被编辑的消息 ID"""

    new_content: MessageChain
    """编辑后的新内容"""

    editor: User
    """编辑者"""

    chat_type: ChatType
    chat_id: typing.Union[int, str]
    group: typing.Optional[Group] = None
```

#### MessageDeletedEvent (`message.deleted`)

消息被删除/撤回。

```python
class MessageDeletedEvent(Event):
    """消息被删除/撤回"""

    type: str = "message.deleted"

    message_id: typing.Union[int, str]
    """被删除的消息 ID"""

    operator: typing.Optional[User] = None
    """操作者（可能是发送者自己撤回，也可能是管理员删除）"""

    chat_type: ChatType
    chat_id: typing.Union[int, str]
    group: typing.Optional[Group] = None
```

#### MessageReactionEvent (`message.reaction`)

消息收到表情回应。

```python
class MessageReactionEvent(Event):
    """消息收到表情回应"""

    type: str = "message.reaction"

    message_id: typing.Union[int, str]
    """被回应的消息 ID"""

    user: User
    """回应者"""

    reaction: str
    """回应的表情标识（emoji 或平台特定表情 ID）"""

    is_add: bool
    """True 为添加回应，False 为移除回应"""

    chat_type: ChatType
    chat_id: typing.Union[int, str]
    group: typing.Optional[Group] = None
```

### 3.3 用户反馈事件

#### FeedbackReceivedEvent (`feedback.received`)

用户对 Bot 回复提交反馈。该事件用于承载平台提供的点赞、点踩、取消反馈以及点踩原因等评价信息；典型来源包括企业微信 AI Bot 的 `feedback_event`、飞书卡片按钮回调、Web Embed 的反馈入口等。

```python
class FeedbackReceivedEvent(Event):
    """收到用户反馈"""

    type: str = "feedback.received"

    feedback_id: str
    """平台侧反馈 ID，用于幂等记录或取消反馈"""

    feedback_type: int
    """1 = like, 2 = dislike, 3 = cancel/remove feedback"""

    feedback_content: typing.Optional[str] = None
    """用户填写的自由文本反馈"""

    inaccurate_reasons: typing.Optional[list[str]] = None
    """点踩时平台提供的预设不准确原因"""

    user_id: typing.Optional[str] = None
    """提交反馈的用户 ID"""

    session_id: typing.Optional[str] = None
    """会话 ID，例如 person_xxx 或 group_xxx"""

    message_id: typing.Optional[str] = None
    """被评价的 Bot 回复消息 ID"""

    stream_id: typing.Optional[str] = None
    """流式回复 ID，用于关联 streaming response"""
```

设计约定：

- `feedback_id` 是幂等键；同一个 `feedback_id` 的后续事件应更新已有记录。
- `feedback_type == 3` 表示用户取消/移除反馈，处理器可删除对应记录或标记为取消。
- 如果平台只能给出原始回调 payload，差异字段保留在 `source_platform_object` 或 `PlatformSpecificEvent.data` 中；通用字段仍优先映射到 `FeedbackReceivedEvent`。
- 该事件保留向后兼容映射：EBA 事件可转换为旧的 `FeedbackEvent`，字段语义保持一致。

### 3.4 群组事件

#### MemberJoinedEvent (`group.member_joined`)

新成员加入群组。

```python
class MemberJoinedEvent(Event):
    """新成员加入群组"""

    type: str = "group.member_joined"

    group: Group
    """群组"""

    member: User
    """加入的成员"""

    inviter: typing.Optional[User] = None
    """邀请者（如有）"""

    join_type: typing.Optional[str] = None
    """加入方式：'invite' / 'request' / 'direct' / None"""
```

#### MemberLeftEvent (`group.member_left`)

成员离开群组。

```python
class MemberLeftEvent(Event):
    """成员离开群组"""

    type: str = "group.member_left"

    group: Group
    member: User

    is_kicked: bool = False
    """是否被踢出"""

    operator: typing.Optional[User] = None
    """操作者（踢出时为管理员）"""
```

#### MemberBannedEvent (`group.member_banned`)

成员被禁言。

```python
class MemberBannedEvent(Event):
    """成员被禁言"""

    type: str = "group.member_banned"

    group: Group
    member: User
    operator: typing.Optional[User] = None
    duration: typing.Optional[int] = None
    """禁言时长（秒），None 表示永久"""
```

#### MemberUnbannedEvent (`group.member_unbanned`)

成员被解除禁言。

```python
class MemberUnbannedEvent(Event):
    """成员被解除禁言"""

    type: str = "group.member_unbanned"

    group: Group
    member: User
    operator: typing.Optional[User] = None
```

#### GroupInfoUpdatedEvent (`group.info_updated`)

群组信息被修改。

```python
class GroupInfoUpdatedEvent(Event):
    """群组信息被修改"""

    type: str = "group.info_updated"

    group: Group
    """更新后的群组信息"""

    operator: typing.Optional[User] = None
    """操作者"""

    changed_fields: list[str] = []
    """发生变更的字段名列表，如 ['name', 'description']"""
```

### 3.5 好友事件

#### FriendRequestReceivedEvent (`friend.request_received`)

收到好友请求。

```python
class FriendRequestReceivedEvent(Event):
    """收到好友请求"""

    type: str = "friend.request_received"

    request_id: typing.Union[int, str]
    """请求 ID，用于后续 approve/reject 操作"""

    user: User
    """请求者"""

    message: typing.Optional[str] = None
    """验证消息"""
```

#### FriendAddedEvent (`friend.added`)

成功添加好友。

```python
class FriendAddedEvent(Event):
    """成功添加好友"""

    type: str = "friend.added"

    user: User
    """新好友"""
```

#### FriendRemovedEvent (`friend.removed`)

好友被移除。

```python
class FriendRemovedEvent(Event):
    """好友被移除"""

    type: str = "friend.removed"

    user: User
    """被移除的好友"""
```

### 3.6 Bot 状态事件

#### BotInvitedToGroupEvent (`bot.invited_to_group`)

Bot 被邀请加入群组。

```python
class BotInvitedToGroupEvent(Event):
    """Bot 被邀请加入群组"""

    type: str = "bot.invited_to_group"

    group: Group
    inviter: typing.Optional[User] = None

    request_id: typing.Optional[typing.Union[int, str]] = None
    """邀请请求 ID，某些平台需要 Bot 确认才加入"""
```

#### BotRemovedFromGroupEvent (`bot.removed_from_group`)

Bot 被移出群组。

```python
class BotRemovedFromGroupEvent(Event):
    """Bot 被移出群组"""

    type: str = "bot.removed_from_group"

    group: Group
    operator: typing.Optional[User] = None
```

#### BotMutedEvent / BotUnmutedEvent (`bot.muted` / `bot.unmuted`)

Bot 被禁言/解除禁言。

```python
class BotMutedEvent(Event):
    """Bot 被禁言"""

    type: str = "bot.muted"

    group: Group
    operator: typing.Optional[User] = None
    duration: typing.Optional[int] = None


class BotUnmutedEvent(Event):
    """Bot 被解除禁言"""

    type: str = "bot.unmuted"

    group: Group
    operator: typing.Optional[User] = None
```

### 3.7 平台特有事件

对于无法抽象为通用事件的平台特有事件，使用统一的 `PlatformSpecificEvent` 承载：

```python
class PlatformSpecificEvent(Event):
    """平台特有事件

    适配器无法映射到通用事件类型时，使用此类型承载。
    插件可以通过 adapter_name + action 来识别和处理。
    """

    type: str = "platform.specific"

    action: str
    """平台特有的事件动作标识，如 'channel_created', 'pin_message'"""

    data: dict = {}
    """事件数据，结构由具体适配器定义"""
```

事件类型字符串格式为 `platform.{adapter_name}.{action}`，例如：
- `platform.telegram.chat_member_updated` — Telegram 的群成员信息更新
- `platform.discord.channel_created` — Discord 的频道创建
- `platform.discord.voice_state_update` — Discord 的语音状态变更
- `platform.slack.app_home_opened` — Slack 的 App Home 打开

## 4. 各平台事件支持矩阵

下表标注各通用事件在主要平台上的支持情况：

| 事件 | Telegram | Discord | OneBot(QQ) | 飞书 | 钉钉 | Slack | 微信 | LINE | KOOK |
|------|----------|---------|-----------|------|------|-------|------|------|------|
| `message.received` | Y | Y | Y | Y | Y | Y | Y | Y | Y |
| `message.edited` | Y | Y | N | Y | N | Y | N | N | Y |
| `message.deleted` | Y | Y | Y | Y | N | Y | Y | N | Y |
| `message.reaction` | Y | Y | Y | Y | Y | Y | N | N | Y |
| `feedback.received` | N | N | N | Y | N | N | Y | N | N |
| `group.member_joined` | Y | Y | Y | Y | Y | Y | Y | Y | Y |
| `group.member_left` | Y | Y | Y | Y | Y | Y | Y | Y | Y |
| `group.member_banned` | Y | Y | Y | N | N | N | N | N | N |
| `group.info_updated` | Y | Y | Y | Y | Y | Y | N | N | Y |
| `friend.request_received` | N | Y | Y | N | N | N | Y | Y | Y |
| `friend.added` | N | Y | Y | N | N | N | Y | Y | N |
| `bot.invited_to_group` | Y | Y | Y | Y | Y | Y | Y | N | Y |
| `bot.removed_from_group` | Y | Y | Y | Y | N | N | Y | N | Y |
| `bot.muted` | Y | N | Y | N | N | N | N | N | N |
| `bot.unmuted` | Y | N | Y | N | N | N | N | N | N |
| `platform.specific` | Y | Y | Y | Y | Y | Y | Y | Y | Y |

> 注：此表为初步评估，具体以各平台 SDK/API 文档为准，实施时逐个确认。

## 5. 事件生命周期

```
1. 平台 SDK 回调触发
      │
2. 适配器 EventConverter.target2yiri(raw_event)
      │  将平台原生事件转换为统一 Event 对象
      │  无法映射的事件 → PlatformSpecificEvent
      │
3. 适配器回调注册的 listener(event, adapter)
      │
4. RuntimeBot 接收事件
      │
5. EventBus 分发
      │
6. EventRouter 查询 Bot 的 event_handlers 配置
      │  匹配事件类型 → 找到对应的 Handler
      │  支持通配符：'message.*' 匹配所有消息事件
      │  未匹配到 → 走默认 Handler（plugin，保持向后兼容）
      │
7. Handler 处理事件
      │  PipelineHandler → 进入 Pipeline 流水线
      │  AgentHandler    → 调用 RequestRunner
      │  WebhookHandler  → POST 到外部 URL
      │  PluginHandler   → 分发给插件 EventListener
      │
8. Handler 执行完毕，可能通过 API 执行响应动作
      （发消息、编辑消息、踢人、同意好友请求等）
```

## 6. 与现有事件类型的兼容映射

为保证现有插件不受影响，建立以下映射关系：

| 新事件 | 条件 | 旧事件 |
|--------|------|--------|
| `MessageReceivedEvent` (chat_type=private) | — | `FriendMessage` |
| `MessageReceivedEvent` (chat_type=group) | — | `GroupMessage` |

在插件 SDK 层面：

| 新事件 | 旧插件事件 |
|--------|-----------|
| `MessageReceivedEvent` (chat_type=private, 非命令) | `PersonNormalMessageReceived` |
| `MessageReceivedEvent` (chat_type=group, 非命令) | `GroupNormalMessageReceived` |
| `MessageReceivedEvent` (chat_type=private, 命令) | `PersonCommandSent` |
| `MessageReceivedEvent` (chat_type=group, 命令) | `GroupCommandSent` |
| `MessageReceivedEvent` (处理完毕后) | `NormalMessageResponded` |

兼容层在事件分发给插件 EventListener 时自动生成旧格式事件，确保监听旧事件类型的插件仍能正常工作。

## 7. 事件类型注册表

适配器在 manifest.yaml 中声明自己支持的事件类型：

```yaml
kind: MessagePlatformAdapter
metadata:
  name: telegram
spec:
  supported_events:
    - message.received
    - message.edited
    - message.deleted
    - message.reaction
    - feedback.received
    - group.member_joined
    - group.member_left
    - group.member_banned
    - group.info_updated
    - bot.invited_to_group
    - bot.removed_from_group
    - bot.muted
    - bot.unmuted
    - platform.specific
  platform_specific_events:
    - chat_member_updated
    - chat_join_request
```

这份声明用于：
1. WebUI 在配置事件处理器时，只显示当前 Bot 的适配器支持的事件类型
2. EventRouter 在路由时校验事件类型有效性
3. 文档自动生成
