# Technical Documentation: hicccc77/WeFlow

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/hicccc77/WeFlow)

### Overview (`README.md`)

<p align="center">
  <img src="app.jpg" alt="WeFlow 应用预览" width="90%">
</p>

<h1 align="center">WeFlow</h1>

<p align="center">
  WeFlow 是一个<strong>完全本地</strong>的<strong>实时</strong>聊天记录查看、分析与导出工具。<br>
  它可以获取你的聊天记录并将其导出，还可以根据你的聊天记录为你生成独一无二的数据与年度报告。
</p>

<p align="center">
  <a href="https://github.com/hicccc77/WeFlow/stargazers"><img src="https://img.shields.io/github/stars/hicccc77/WeFlow?style=flat&label=Stars&labelColor=2A3B4C&color=60A5FA" alt="Stargazers"></a>
  <a href="https://github.com/hicccc77/WeFlow/network/members"><img src="https://img.shields.io/github/forks/hicccc77/WeFlow?style=flat&label=Forks&labelColor=2A3B4C&color=60A5FA" alt="Forks"></a>
  <a href="https://github.com/hicccc77/WeFlow/releases"><img src="https://img.shields.io/github/downloads/hicccc77/WeFlow/total?style=flat&label=Downloads&labelColor=2A3B4C&color=60A5FA" alt="Downloads"></a>
  <br><br>
  <a href="https://t.me/weflow_cc"><img src="https://img.shields.io/badge/Telegram-频道-60A5FA?style=flat&logo=telegram&logoColor=white&labelColor=2A3B4C&color=60A5FA" alt="Telegram Channel" style="height: 24px; vertical-align: middle;"></a>
  <a href="https://star-history.com/#hicccc77/WeFlow"><img src="https://api.star-history.com/badge?repo=hicccc77/WeFlow&theme=dark" alt="Star History Rank" style="height: 30px; vertical-align: middle;"></a>
</p>

> [!TIP]
> 需要配套的第三方可插拔组件（本项目依赖自定义的原生解密组件，需自行设计，见 [docs/third-party-components.md](docs/third-party-components.md)）


## 主要功能

- 本地实时查看聊天记录
- 朋友圈图片、视频、**实况**的预览和解密
- 统计分析与群聊画像
- 年度报告与可视化概览
- 导出聊天记录为 HTML 等格式
- HTTP API 接口（面向开发者）
- 查看完整能力清单：[详细功能](#详细功能清单)

## 支持平台与设备

| 平台 | 设备/架构 | 安装包 |
|------|----------|--------|
| Windows | Windows10+、x64 | `.exe` |
| macOS | Apple Silicon（M 系列，arm64） | `.dmg` |
| Linux | x64 设备（amd64） | `.AppImage`、`.tar.gz` |


## 详细功能清单

| 功能模块 | 说明 |
|---------|------|
| **聊天** | 解密聊天中的图片、视频、实况；支持**修改**本地消息 |
| **消息防撤回** | 防止其他人发送的消息被撤回 |
| **实时弹窗通知** | 新消息到达时提供桌面弹窗提醒，便于及时查看重要会话，提供黑白名单功能 |
| **私聊分析** | 统计好友间消息数量；分析消息类型与发送比例；查看消息时段分布等 |
| **群聊分析** | 查看群成员详细信息；分析群内发言排行、活跃时段和媒体内容 |
| **年度报告** | 生成按年统计的年度报告，或跨年度的长期历史报告 |
| **双人报告** | 选择指定好友，基于双方聊天记录生成专属分析报告 |
| **消息导出** | 将聊天记录导出为多种格式：JSON、HTML、Markdown、TXT、Excel、CSV、PGSQL、ChatLab专属格式等 |
| **朋友圈** | 解密朋友圈图片、视频、实况；导出朋友圈内容；拦截朋友圈的删除与隐藏操作； |
| **联系人** | 导出好友、群聊、公众号信息；找回部分曾经的好友 |
| **HTTP API 映射** | 将本地消息能力映射为 HTTP API，便于对接外部系统、自动化脚本与二次开发 |

## HTTP API

WeFlow 提供本地 HTTP API 服务，支持通过接口查询消息数据，可用于与其他工具集成或二次开发。

- **启用方式**：设置 → API 服务 → 启动服务
- **默认端口**：5031
- **访问地址**：`http://127.0.0.1:5031`
- **支持格式**：原始 JSON 或 [ChatLab](https://chatlab.fun/) 标准格式

完整接口文档：[点击查看](docs/HTTP-API.md)

## 面向开发者

如果你想从源码构建或为项目贡献代码，请遵循以下步骤：

```bash
# 1. 克隆项目到本地
git clone https://github.com/hicccc77/WeFlow.git
cd WeFlow

# 2. 安装项目依赖
npm install

# 3. 运行应用（开发模式）
npm run dev
```

## 推广与合作

如果您对 **WeFlow** 有兴趣，或者希望与我们展开深度合作或投放你的广告，欢迎随时通过邮件取得联系。我们非常期待与各位创作者、开发者及合作伙伴共同探索。

### 联系方式

欢迎发送邮件至：

<a href="mailto:yccccccy@proton.me"><img src="https://img.shields.io/badge/Email-yccccccy%40proton.me-60A5FA?style=flat-square&logo=proton&logoColor=white&labelColor=2A3B4C" alt="Protonmail" height="24px"></a>

## 合作伙伴

我们非常欢迎优秀的开源社区项目团队或其他团队与我们建立长期合作关系。期待与 WeFlow 携手并进，共同建设更开放的生态！

<p align="center">
  
</p>

---

## 贡献者

感谢所有做出贡献的开发者！

<p align="center">
  <a href="https://github.com/hicccc77/WeFlow/graphs/contributors">
    <img src="https://contrib.rocks/image?repo=hicccc77/WeFlow" alt="Contributors" />
  </a>
</p>

## Star History

<a href="https://www.star-history.com/#hicccc77/WeFlow&type=date&legend=top-left">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=hicccc77/WeFlow&type=date&theme=dark&legend=top-left" />
    <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=hicccc77/WeFlow&type=date&legend=top-left" />
    <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=hicccc77/WeFlow&type=date&legend=top-left" />
  </picture>
</a>

<div align="center">

---

**请负责任地使用本工具，遵守相关法律法规**

</div>

---

### HTTP API (`docs/HTTP-API.md`)

# WeFlow HTTP API / Push 文档

WeFlow 提供本地 HTTP API（已支持GET 和 POST请求），便于外部脚本或工具读取聊天记录、会话、联系人、群成员和导出的媒体文件；也支持在检测到新消息后通过固定 SSE 地址主动推送消息事件。

## 启用方式

在应用设置页启用 `API 服务`。

- 默认监听地址：`127.0.0.1`
- 默认端口：`5031`
- 基础地址：`http://127.0.0.1:5031`
- 可选开启 `主动推送`，检测到新收到的消息后会通过 `GET /api/v1/push/messages` 推送给 SSE 订阅端

**状态记忆**：API 服务和主动推送的状态及端口会自动保存，重启 WeFlow 后会自动恢复运行。

## 鉴权规范

**鉴权规范 (Access Token)** 除健康检查接口外，所有 `/api/v1/*` 接口均受 Token 保护。支持三种传参方式（任选其一）：

1. **HTTP Header (推荐)**: `Authorization: Bearer <您的Token>`
2. **Query 参数**: `?access_token=<您的Token>`（SSE 长连接推荐此方式）
3. **JSON Body**: `{"access_token": "<您的Token>"}`（仅限 POST 请求）

## 接口列表

- `GET|POST /health`
- `GET|POST /api/v1/health`
- `GET|POST /api/v1/push/messages`
- `GET|POST /api/v1/messages`
- `GET|POST /api/v1/sessions`
- `GET /api/v1/sessions/:id/messages` (ChatLab Pull)
- `GET|POST /api/v1/contacts`
- `GET|POST /api/v1/group-members`
- `GET|POST /api/v1/media/*`

---

## 1. 健康检查

**请求**

```http
GET /health
```

或

```http
GET /api/v1/health
```

**响应**

```json
{
  "status": "ok"
}
```

---

## 2. 主动推送

通过 SSE 长连接接收新消息事件，端口与 HTTP API 共用。

**请求**

```http
GET /api/v1/push/messages
```

### 说明

- 需要先在设置页开启 `HTTP API 服务`
- 同时需要开启 `主动推送`
- 响应类型为 `text/event-stream`
- 事件名包含 `message.new` 和 `message.revoke`
- 建议接收端按 `event + rawid` 去重

### 事件字段

- `event`
- `sessionId`
- `rawid`
- `avatarUrl`
- `sourceName`
- `groupName`（仅群聊）
- `content`
- `timestamp`（消息时间，秒级 Unix 时间戳）

### 示例

```bash
curl -N "http://127.0.0.1:5031/api/v1/push/messages?access_token=YOUR_TOKEN
```

示例事件：

```text
event: message.new
data: {"event":"message.new","sessionId":"xxx@chatroom","sessionType":"group","rawid":"1234567890123456789","avatarUrl":"https://example.com/group.jpg","sourceName":"李四","groupName":"项目群","content":"[图片]","timestamp":1760000123}
```

撤回事件示例：

```text
event: message.revoke
data: {"event":"message.revoke","sessionId":"wxid_xxx","sessionType":"other","rawid":"1234567890123456789","avatarUrl":"https://example.com/avatar.jpg","sourceName":"张三","content":"对方撤回了一条消息（rawid：1234567890123456789） 内容为“你好”","timestamp":1760000180}
```

---

## 3. 获取消息

> 当使用 POST 时，请将参数放在 JSON Body 中（Content-Type: application/json）

读取指定会话的消息，支持原始 JSON 和 ChatLab 格式。

**请求**

```http
GET /api/v1/messages
```

### 参数

| 参数      | 类型   | 必填 | 说明                                                  |
| --------- | ------ | ---- | ----------------------------------------------------- |
| `talker`  | string | 是   | 会话 ID。私聊通常是对方 `wxid`，群聊是 `xxx@chatroom` |
| `limit`   | number | 否   | 返回条数，默认 `100`，范围 `1~10000`                  |
| `offset`  | number | 否   | 分页偏移，默认 `0`                                    |
| `start`   | string | 否   | 开始时间，支持 `YYYYMMDD` 或时间戳                    |
| `end`     | string | 否   | 结束时间，支持 `YYYYMMDD` 或时间戳                    |
| `keyword` | string | 否   | 基于消息显示文本过滤                                  |
| `chatlab` | string | 否   | `1/true` 时输出 ChatLab 格式                          |
| `format`  | string | 否   | `json` 或 `chatlab`                                   |
| `media`   | string | 否   | `1/true` 时导出媒体并返回媒体地址，兼容别名 `meiti`   |
| `image`   | string | 否   | 在 `media=1` 时控制图片导出，兼容别名 `tupian`        |
| `voice`   | string | 否   | 在 `media=1` 时控制语音导出，兼容别名 `vioce`         |
| `video`   | string | 否   | 在 `media=1` 时控制视频导出                           |
| `emoji`   | string | 否   | 在 `media=1` 时控制表情导出                           |

### 示例

```bash
curl "http://127.0.0.1:5031/api/v1/messages?talker=wxid_xxx&limit=20"
curl "http://127.0.0.1:5031/api/v1/messages?talker=xxx@chatroom&chatlab=1"
curl "http://127.0.0.1:5031/api/v1/messages?talker=wxid_xxx&start=20260101&end=20260131"
curl "http://127.0.0.1:5031/api/v1/messages?talker=xxx@chatroom&media=1&image=1&voice=0&video=0&emoji=0"
```

### JSON 响应字段

顶层字段：

- `success`
- `talker`
- `count`
- `hasMore`
- `media.enabled`
- `media.exportPath`
- `media.count`
- `messages`

单条消息字段：

- `localId`
- `serverId`
- `localType`
- `createTime`
- `isSend`
- `senderUsername`
- `content`
- `rawContent`
- `parsedContent`
- `replyToMessageId`（引用回复目标消息的 `serverId`，仅引用消息返回）
- `quote`（引用消息快照，包含被引用消息的 ID、发送者、内容和类型）
- `mediaType`
- `mediaFileName`
- `mediaUrl`
- `mediaLocalPath`

**示例响应**

```json
{
  "success": true,
  "talker": "xxx@chatroom",
  "count": 3,
  "hasMore": true,
  "media": {
    "enabled": true,
    "exportPath": "C:\\Users\\Alice\\Documents\\WeFlow\\api-media",
    "count": 1
  },
  "messages": [
    {
      "localId": 123,
      "serverId": "6116895530414915131",
      "localType": 1,
      "createTime": 1738713600,
      "isSend": 0,
      "senderUsername": "wxid_member",
      "content": "你好",
      "rawContent": "你好",
      "parsedContent": "你好"
    },
    {
      "localId": 125,
      "serverId": "6116895530414915133",
      "localType": 244813135921,
      "createTime": 1738713700,
      "isSend": 0,
      "senderUsername": "wxid_member",
      "content": "收到",
      "rawContent": "<msg>...</msg>",
      "parsedContent": "收到",
      "replyToMessageId": "6116895530414915131",
      "quote": {
        "platformMessageId": "6116895530414915131",
        "sender": "wxid_other",
        "accountName": "张三",
        "content": "你好",
        "type": 0
      }
    },
    {
      "localId": 124,
      "localType": 3,
      "createTime": 1738713660,
      "isSend": 0,
      "senderUsername": "wxid_member",
      "content": "[图片]",
      "mediaType": "image",
      "mediaFileName": "abc123.jpg",
      "mediaUrl": "http://127.0.0.1:5031/api/v1/media/xxx@chatroom/images/abc123.jpg",
      "mediaLocalPath": "C:\\Users\\Alice\\Documents\\WeFlow\\api-media\\xxx@chatroom\\images\\abc123.jpg"
    }
  ]
}
```

### ChatLab 响应

当 `chatlab=1` 或 `format=chatlab` 时，返回 ChatLab 结构：

- `chatlab.version`
- `chatlab.exportedAt`
- `chatlab.generator`
- `meta.name`
- `meta.platform`
- `meta.type`
- `meta.groupId`
- `meta.groupAvatar`
- `meta.ownerId`
- `members[].platformId`
- `members[].accountName`
- `members[].groupNickname`
- `members[].avatar`
- `messages[].sender`
- `messages[].accountName`
- `messages[].groupNickname`
- `messages[].timestamp`
- `messages[].type`
- `messages[].content`
- `messages[].platformMessageId`
- `messages[].replyToMessageId`
- `messages[].mediaPath`

群聊里 `groupNickname` 会优先来自群成员群昵称；若源数据缺失，则回退为空或展示名。

---

## 4. 获取会话列表

> 当使用 POST 时，请将参数放在 JSON Body 中（Content-Type: application/json）

**请求**

```http
GET /api/v1/sessions
```

### 参数

| 参数      | 类型   | 必填 | 说明                             |
| --------- | ------ | ---- | -------------------------------- |
| `keyword` | string | 否   | 匹配 `username` 或 `displayName` |
| `limit`   | number | 否   | 默认 `100`                       |

### 响应字段

- `success`
- `count`
- `sessions[].username`
- `sessions[].displayName`
- `sessions[].type`
- `sessions[].lastTimestamp`
- `sessions[].unreadCount`

**示例响应**

```json
{
  "success": true,
  "count": 1,
  "sessions": [
    {
      "username": "xxx@chatroom",
      "displayName": "项目群",
      "type": 2,
      "lastTimestamp": 1738713600,
      "unreadCount": 0
    }
  ]
}
```

---

## 4.1 获取会话列表（ChatLab 格式）

当 `format=chatlab` 时，返回 ChatLab Pull 协议兼容格式，可直接作为 ChatLab 远程数据源。

**请求**

```http
GET /api/v1/sessions?format=chatlab
```

### 参数

| 参数      | 类型   | 必填 | 说明                             |
| --------- | ------ | ---- | -------------------------------- |
| `format`  | string | 是   | 设为 `chatlab`                   |
| `keyword` | string | 否   | 匹配 `username` 或 `displayName` |
| `limit`   | number | 否   | 默认 `100`                       |

### 响应

```json
{
  "sessions": [
    {
      "id": "xxx@chatroom",
      "name": "项目群",
      "platform": "im",
      "type": "group",
      "messageCount": 58000,
      "lastMessageAt": 1738713600
    }
  ]
}
```

| 字段            | 说明                                |
| --------------- | ----------------------------------- |
| `id`            | 会话 ID（username）            |
| `name`          | 会话显示名称                        |
| `platform`      | 固定 `im`                       |
| `type`          | `group`（群聊）或 `private`（私聊） |
| `messageCount`  | 消息数量（估算值，可能不精确）      |
| `lastMessageAt` | 最后消息的秒级 Unix 时间戳          |

---

## 4.2 拉取会话消息（ChatLab Pull）

返回 ChatLab 标准格式的聊天数据，支持增量拉取和分页。

**请求**

```http
GET /api/v1/sessions/:id/messages
```

### 参数

| 参数     | 类型   | 必填 | 说明                                     |
| -------- | ------ | ---- | ---------------------------------------- |
| `:id`    | string | 是   | 会话 ID（Path 参数）                     |
| `since`  | number | 否   | 秒级 Unix 时间戳，仅返回此时间之后的消息 |
| `end`    | number | 否   | 秒级 Unix 时间戳，时间上界               |
| `limit`  | number | 否   | 单次返回上限，默认且最大 `5000`          |
| `offset` | number | 否   | 分页偏移，默认 `0`                       |

### 响应

返回 ChatLab 标准 JSON 格式，外加 `sync` 分页块：

```json
{
  "chatlab": {
    "version": "0.0.2",
    "exportedAt": 1738713600,
    "generator": "WeFlow"
  },
  "meta": {
    "name": "项目群",
    "platform": "im",
    "type": "group",
    "groupId": "xxx@chatroom",
    "ownerId": "wxid_xxx"
  },
  "members": [
    {
      "platformId": "wxid_a",
      "accountName": "张三",
      "groupNickname": "产品",
      "avatar": "https://example.com/avatar.jpg"
    }
  ],
  "messages": [
    {
      "sender": "wxid_a",
      "accountName": "张三",
      "timestamp": 1738713600,
      "type": 0,
      "content": "你好",
      "platformMessageId": "123456"
    }
  ],
  "sync": {
    "hasMore": true,
    "nextSince": 1738713600,
    "nextOffset": 5000,
    "watermark": 1738714000
  }
}
```

### sync 块

| 字段         | 说明                             |
| ------------ | -------------------------------- |
| `hasMore`    | 是否还有更多数据                 |
| `nextSince`  | 下次请求的 `since` 值            |
| `nextOffset` | 下次请求的 `offset` 值           |
| `watermark`  | 本次拉取的时间上界（秒级时间戳） |

**ChatLab 对接方式**：在 ChatLab 设置中添加远程数据源，`baseUrl` 填 `http://127.0.0.1:5031/api/v1`，Token 填 WeFlow 中配置的 API Token。

---

## 5. 获取联系人列表

> 当使用 POST 时，请将参数放在 JSON Body 中（Content-Type: application/json）

**请求**

```http
GET /api/v1/contacts
```

### 参数

| 参数      | 类型   | 必填 | 说明                                                 |
| --------- | ------ | ---- | ---------------------------------------------------- |
| `keyword` | string | 否   | 匹配 `username`、`nickname`、`remark`、`displayName` |
| `limit`   | number | 否   | 默认 `100`                                           |

### 响应字段

- `success`
- `count`
- `contacts[].username`
- `contacts[].displayName`
- `contacts[].remark`
- `contacts[].nickname`
- `contacts[].alias`
- `contacts[].avatarUrl`
- `contacts[].type`

**示例响应**

```json
{
  "success": true,
  "count": 1,
  "contacts": [
    {
      "username": "wxid_xxx",
      "displayName": "张三",
      "remark": "客户张三",
      "nickname": "张三",
      "alias": "zhangsan",
      "avatarUrl": "https://example.com/avatar.jpg",
      "type": "friend"
    }
  ]
}
```

---

## 6. 获取群成员列表

> 当使用 POST 时，请将参数放在 JSON Body 中（Content-Type: application/json）

返回群成员的 `accountId`、群昵称、备注、账号号等信息。

**请求**

```http
GET /api/v1/group-members
```

### 参数

| 参数                   | 类型   | 必填 | 说明                            |
| ---------------------- | ------ | ---- | ------------------------------- |
| `chatroomId`           | string | 是   | 群 ID，兼容使用 `talker` 传入   |
| `includeMessageCounts` | string | 否   | `1/true` 时附带成员发言数       |
| `withCounts`           | string | 否   | `includeMessageCounts` 的别名   |
| `forceRefresh`         | string | 否   | `1/true` 时跳过内存缓存强制刷新 |

### 响应字段

- `success`
- `chatroomId`
- `count`
- `fromCache`
- `updatedAt`
- `members[].wxid`
- `members[].displayName`
- `members[].nickname`
- `members[].remark`
- `members[].alias`
- `members[].groupNickname`
- `members[].avatarUrl`
- `members[].isOwner`
- `members[].isFriend`
- `members[].messageCount`

**示例请求**

```bash
curl "http://127.0.0.1:5031/api/v1/group-members?chatroomId=xxx@chatroom"
curl "http://127.0.0.1:5031/api/v1/group-members?chatroomId=xxx@chatroom&includeMessageCounts=1&forceRefresh=1"
```

**示例响应**

```json
{
  "success": true,
  "chatroomId": "xxx@chatroom",
  "count": 2,
  "fromCache": false,
  "updatedAt": 1760000000000,
  "members": [
    {
      "wxid": "wxid_member_a",
      "displayName": "客户A",
      "nickname": "阿甲",
      "remark": "客户A",
      "alias": "kehua",
      "groupNickname": "甲方",
      "avatarUrl": "https://example.com/a.jpg",
      "isOwner": true,
      "isFriend": true,
      "messageCount": 128
    },
    {
      "wxid": "wxid_member_b",
      "displayName": "李四",
      "nickname": "李四",
      "remark": "",
      "alias": "",
      "groupNickname": "",
      "avatarUrl": "",
      "isOwner": false,
      "isFriend": false,
      "messageCount": 0
    }
  ]
}
```

说明：

- `displayName` 是当前应用内的主展示名。
- `groupNickname` 是成员在该群里的群昵称。
- `remark` 是你对该联系人的备注。
- `alias` 是账号号。
- 当源数据里没有群昵称时，`groupNickname` 会为空。

---

## 7. 朋友圈接口

### 7.1 获取朋友圈时间线

```http
GET /api/v1/sns/timeline
```

参数：

| 参数        | 类型   | 必填 | 说明                                                         |
| ----------- | ------ | ---- | ------------------------------------------------------------ |
| `limit`     | number | 否   | 返回数量，默认 20，范围 `1~200`                              |
| `offset`    | number | 否   | 偏移量，默认 0                                               |
| `usernames` | string | 否   | 发布者过滤，逗号分隔，如 `wxid_a,wxid_b`                     |
| `keyword`   | string | 否   | 关键词过滤（正文）                                           |
| `start`     | string | 否   | 开始时间，支持 `YYYYMMDD` 或秒/毫秒时间戳                    |
| `end`       | string | 否   | 结束时间，支持 `YYYYMMDD` 或秒/毫秒时间戳                    |
| `media`     | number | 否   | 是否返回可直接访问的媒体地址，默认 `1`                       |
| `replace`   | number | 否   | `media=1` 时，是否用解析地址覆盖 `media.url/thumb`，默认 `1` |
| `inline`    | number | 否   | `media=1` 时，是否内联返回 `data:` URL，默认 `0`             |

示例：

```bash
curl "http://127.0.0.1:5031/api/v1/sns/timeline?limit=5"
curl "http://127.0.0.1:5031/api/v1/sns/timeline?usernames=wxid_a,wxid_b&keyword=旅行"
curl "http://127.0.0.1:5031/api/v1/sns/timeline?limit=3&media=1&replace=1"
curl "http://127.0.0.1:5031/api/v1/sns/timeline?limit=3&media=1&inline=1"
```

媒体字段说明（`media=1`）：

- `media[].url/thumb`：你应该优先直接使用的字段。
- `replace=1`（默认）时，`media[].url/thumb` 会直接被替换成可访问地址，等价于 `resolvedUrl/resolvedThumbUrl`。
- `replace=0` 时，`media[].url/thumb` 仍保留原始地址；这时再结合下面的 `raw/proxy/resolved` 字段自己决定用哪一个。
- `media[].rawUrl/rawThumb`：原始朋友圈地址
- `media[].proxyUrl/proxyThumbUrl`：可直接访问的代理地址
- `media[].resolvedUrl/resolvedThumbUrl`：最终可用地址（`inline=1` 时可能是 `data:` URL）
- `media[].token/key/encIdx`：源数据里的访问/解密参数。通常不需要你自己处理；如果你手动调用 `/api/v1/sns/media/proxy`，把当前条目的 `url` 和 `key` 原样传回即可。
- `media[].livePhoto`：实况图的视频部分。外层 `media[].url/thumb` 仍是封面图，`livePhoto` 内部会再提供一组自己的 `url/thumb/raw*/proxy*/resolved*` 字段。
- `media=0` 时，不会补充 `raw*/proxy*/resolved*`，接口只返回原始 `url/thumb` 以及源字段（如 `key/token/encIdx`）。

### 7.2 获取朋友圈发布者

```http
GET /api/v1/sns/usernames
```

### 7.3 获取朋友圈导出统计

```http
GET /api/v1/sns/export/stats
```

参数：

| 参数   | 类型   | 必填 | 说明                         |
| ------ | ------ | ---- | ---------------------------- |
| `fast` | number | 否   | `1` 使用快速统计（优先缓存） |

### 7.4 朋友圈媒体代理

```http
GET /api/v1/sns/media/proxy
```

参数：

| 参数  | 类型          | 必填 | 说明                     |
| ----- | ------------- | ---- | ------------------------ |
| `url` | string        | 是   | 媒体原始 URL             |
| `key` | string/number | 否   | 解密 key（部分资源需要） |

### 7.5 导出朋友圈

```http
POST /api/v1/sns/export
Content-Type: application/json
```

Body 示例：

```json
{
  "outputDir": "C:\\Users\\Alice\\Desktop\\sns-export",
  "format": "json",
  "usernames": "wxid_a,wxid_b",
  "keyword": "旅行",
  "exportMedia": true,
  "exportImages": true,
  "exportLivePhotos": true,
  "exportVideos": true,
  "start": "20250101",
  "end": "20251231"
}
```

`format` 支持：`json`、`html`、`arkmejson`（兼容写法：`arkme-json`）、`markdown`（兼容写法：`md`）。

### 7.6 朋友圈防删开关

```http
GET  /api/v1/sns/block-delete/status
POST /api/v1/sns/block-delete/install
POST /api/v1/sns/block-delete/uninstall
```

### 7.7 删除单条朋友圈

```http
DELETE /api/v1/sns/post/{postId}
```

---

## 8. 访问导出媒体

> 当使用 POST 时，请将参数放在 JSON Body 中（Content-Type: application/json）

通过消息接口启用 `media=1` 后，接口会先把图片、语音、视频、表情导出到本地缓存目录，再返回可访问的 HTTP 地址。

**请求**

```http
GET /api/v1/media/{relativePath}
```

### 示例

```bash
curl "http://127.0.0.1:5031/api/v1/media/xxx@chatroom/images/abc123.jpg"
curl "http://127.0.0.1:5031/api/v1/media/xxx@chatroom/voices/voice_100.wav"
curl "http://127.0.0.1:5031/api/v1/media/xxx@chatroom/videos/video_200.mp4"
curl "http://127.0.0.1:5031/api/v1/media/xxx@chatroom/emojis/emoji_300.gif"
```

### 支持的 Content-Type

| 扩展名           | Content-Type |
| ---------------- | ------------ |
| `.png`           | `image/png`  |
| `.jpg` / `.jpeg` | `image/jpeg` |
| `.gif`           | `image/gif`  |
| `.webp`          | `image/webp` |
| `.wav`           | `audio/wav`  |
| `.mp3`           | `audio/mpeg` |
| `.mp4`           | `video/mp4`  |

常见错误响应：

```json
{
  "error": "Media not found"
}
```

---

## 9. 使用示例

### PowerShell

```powershell
$headers = @{ "Authorization" = "Bearer YOUR_TOKEN" }
$body = @{ talker = "wxid_xxx"; limit = 10 } | ConvertTo-Json

Invoke-RestMethod -Uri "http://127.0.0.1:5031/api/v1/messages" -Method POST -Headers $headers -Body $body -ContentType "application/json"
```

### cURL

```bash
# GET 带 Token Header
curl -H "Authorization: Bearer YOUR_TOKEN" "http://127.0.0.1:5031/api/v1/messages?talker=wxid_xxx"

# POST 带 JSON Body
curl -X POST http://127.0.0.1:5031/api/v1/messages \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"talker": "xxx@chatroom", "chatlab": true}'
```

### Python

```python
import requests

BASE_URL = "http://127.0.0.1:5031"
headers = {"Authorization": "Bearer YOUR_TOKEN", "Content-Type": "application/json"}

# POST 方式获取消息
messages = requests.post(
    f"{BASE_URL}/api/v1/messages",
    json={"talker": "xxx@chatroom", "limit": 50},
    headers=headers
).json()

# GET 方式获取群成员
members = requests.get(
    f"{BASE_URL}/api/v1/group-members",
    params={"chatroomId": "xxx@chatroom", "includeMessageCounts": 1},
    headers=headers
).json()
```

---

## 10. 注意事项

1. API 仅监听本机 `127.0.0.1`，不对外网开放。
2. 使用前需要先在 WeFlow 中完成数据库连接。
3. `start` 和 `end` 支持 `YYYYMMDD` 与时间戳；纯 `YYYYMMDD` 的 `end` 会扩展到当天 `23:59:59`。
4. 群成员的 `groupNickname` 依赖源数据；源数据缺失时不会自动补出。
5. 媒体访问链接只有在对应消息已经通过 `media=1` 导出后才可访问。

---

### Third Party Components (`docs/third-party-components.md`)

# 第三方可插拔组件接口说明

WeFlow 不再内置任何用于读取/解密本地数据的原生组件。以下四类能力全部由用户在
「设置 -> 数据库 -> 第三方组件路径」中自行配置一个可执行文件/动态库/插件路径，
应用只按下述协议与其交互，不对其来源、签名或实现方式做任何校验。

留空或指向的文件不存在时，对应功能会直接返回"未配置"错误，不会有任何降级或替代实现。

## 1. WCDB 实现（`wcdbLibPath`）

一个动态库（Windows `*.dll` / macOS `*.dylib` / Linux `*.so`），通过 [koffi](https://koffi.dev/)
以 C ABI 方式加载，用于读取本地 SQLCipher 数据库并提供会话/消息/联系人/朋友圈等查询。

加载流程（见 `electron/services/wcdbCore.ts` 的 `initialize()`）：
1. `koffi.load(wcdbLibPath)`。
2. 依次尝试绑定下列导出函数；可选符号不存在时该功能会被跳过（返回 `null`），不影响其余功能。
   `wcdb_init`、`wcdb_shutdown`、`wcdb_open_account`、`wcdb_close_account` 和
   `wcdb_free_string` 必须存在，用于初始化、账号生命周期和返回字符串的释放；
   查询、统计、媒体、日志等功能接口按需实现即可。

完整函数签名列表（`int32` 返回值均为状态码，`0` 表示成功；`_Out_ void** outJson` 类参数
由组件分配字符串内存，调用方通过 `wcdb_free_string` 释放）：

```
/* Detailed source-code truncated for AI context efficiency. */
```

上述五个生命周期与内存管理接口之外均为可选：缺失的符号对应功能会在应用里表现为"不可用"，
不影响其他已实现的功能。消息游标需要同时提供 `wcdb_open_message_cursor`、
`wcdb_fetch_message_batch` 和 `wcdb_close_message_cursor`，以保证已打开的游标可以读取和释放。
JSON 载荷/出参的字段命名可参照 `electron/services/wcdbCore.ts` 里
每个函数调用点前后对返回值的解析逻辑。

## 2. 媒体解密插件（`imageNativeAddonPath`）

一个 Node 原生插件（`.node`，通过 `require()` 加载），对应 `electron/services/nativeImageDecrypt.ts`
里的 `NativeAddon` 接口：

```ts
{
  decryptDatNative(inputPath: string, xorKey: number, aesKey?: string): {
    data: Buffer
    ext: string          // 如 'jpg'/'png'/'mp4'，不含点号也可
    isWxgf?: boolean      // 是否为 WXGF 容器格式，是则会再走一层 unwrap
    version?: number
    aesSize?: number
    xorSize?: number
    rawSize?: number
    flag?: number
  }
  // 可选：编辑/重新加密场景使用
  encryptDatNative?(inputPath: string, xorKey: number, aesKey?: string, meta?: object): Buffer
}
```

参照原始 `Wedecrypt/` Rust 工程（`napi`/`napi-derive` + `cdylib`）实现即可复用其思路。

## 3. WeLive 批量导出引擎（`welivePath`）

一个可执行文件，由 `electron/services/weliveBridge.ts` 的 `runWeliveExport()` 拉起，
用于会话的批量原始导出（文本 + 媒体）。协议：

- 请求：完整的 `WeliveExportRequest`（见 `weliveBridge.ts` 内类型定义，包含账号信息、
  `sessionIds`、输出目录、媒体类型等）序列化为 JSON，写入子进程 stdin 后关闭。
- 响应：子进程通过 stdout 按行输出 `WeliveExportEvent` 的 NDJSON（`ready`/`progress`/
  `created_file`/`created_dir`/`session_error`/`result` 等类型），最终以一条 `result` 事件
  （含 `success`/`success_count`/`fail_count`/`session_output_paths`/`raw_export_manifests`
  等字段）结束。进程以退出码 `0` 且最后一条事件为 `result` 且 `success !== false` 视为成功。
- 支持通过关闭 stdin/发送信号来响应取消（`AbortSignal`），进程应能在收到终止信号后尽快退出。

---

--- METRICS ---
- Files Extracted: 3
- Estimated Token Budget: ~6222 tokens
- Canonical Reference: https://codewiki.google/github.com/hicccc77/WeFlow
