diff --git a/.gitignore b/.gitignore index 5cc4ff8..cccee17 100644 --- a/.gitignore +++ b/.gitignore @@ -57,4 +57,5 @@ Thumbs.db wcdb/ *info -*.md +概述.md +chatlab-format.md diff --git a/README.md b/README.md index 6264249..de25ae8 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,22 @@ WeFlow 是一个**完全本地**的微信**实时**聊天记录查看、分析 - 统计分析与群聊画像 - 年度报告与可视化概览 - 导出聊天记录为 HTML 等格式 +- HTTP API 接口(供开发者集成) + + +## HTTP API + +> [!WARNING] +> 此功能目前处于早期阶段,接口可能会有变动,请等待后续更新完善。 + +WeFlow 提供本地 HTTP API 服务,支持通过接口查询消息数据,可用于与其他工具集成或二次开发。 + +- **启用方式**:设置 → API 服务 → 启动服务 +- **默认端口**:5031 +- **访问地址**:`http://127.0.0.1:5031` +- **支持格式**:原始 JSON 或 [ChatLab](https://chatlab.fun/) 标准格式 + +📖 完整接口文档:[docs/HTTP-API.md](docs/HTTP-API.md) ## 快速开始 diff --git a/docs/HTTP-API.md b/docs/HTTP-API.md new file mode 100644 index 0000000..0a099fe --- /dev/null +++ b/docs/HTTP-API.md @@ -0,0 +1,312 @@ +# WeFlow HTTP API 接口文档 + +WeFlow 提供 HTTP API 服务,支持通过 HTTP 接口查询消息数据,支持 [ChatLab](https://github.com/nichuanfang/chatlab-format) 标准化格式输出。 + +## 启用 API 服务 + +在设置页面 → API 服务 → 点击「启动服务」按钮。 + +默认端口:`5031` + +## 基础地址 + +``` +http://127.0.0.1:5031 +``` + +--- + +## 接口列表 + +### 1. 健康检查 + +检查 API 服务是否正常运行。 + +**请求** +``` +GET /health +``` + +**响应** +```json +{ + "status": "ok" +} +``` + +--- + +### 2. 获取消息列表 + +获取指定会话的消息,支持 ChatLab 格式输出。 + +**请求** +``` +GET /api/v1/messages +``` + +**参数** + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| `talker` | string | ✅ | 会话 ID(wxid 或群 ID) | +| `limit` | number | ❌ | 返回数量限制,默认 100 | +| `offset` | number | ❌ | 偏移量,用于分页,默认 0 | +| `start` | string | ❌ | 开始时间,格式 YYYYMMDD | +| `end` | string | ❌ | 结束时间,格式 YYYYMMDD | +| `chatlab` | string | ❌ | 设为 `1` 则输出 ChatLab 格式 | +| `format` | string | ❌ | 输出格式:`json`(默认)或 `chatlab` | + +**示例请求** + +```bash +# 获取消息(原始格式) +GET http://127.0.0.1:5031/api/v1/messages?talker=wxid_xxx&limit=50 + +# 获取消息(ChatLab 格式) +GET http://127.0.0.1:5031/api/v1/messages?talker=wxid_xxx&chatlab=1 + +# 带时间范围查询 +GET http://127.0.0.1:5031/api/v1/messages?talker=wxid_xxx&start=20260101&end=20260205&limit=100 +``` + +**响应(原始格式)** +```json +{ + "success": true, + "talker": "wxid_xxx", + "count": 50, + "hasMore": true, + "messages": [ + { + "localId": 123, + "talker": "wxid_xxx", + "type": 1, + "content": "消息内容", + "createTime": 1738713600000, + "isSelf": false, + "sender": "wxid_sender" + } + ] +} +``` + +**响应(ChatLab 格式)** +```json +{ + "chatlab": { + "version": "0.0.2", + "exportedAt": 1738713600000, + "generator": "WeFlow", + "description": "Exported from WeFlow" + }, + "meta": { + "name": "会话名称", + "platform": "wechat", + "type": "private", + "ownerId": "wxid_me" + }, + "members": [ + { + "platformId": "wxid_xxx", + "accountName": "用户名", + "groupNickname": "群昵称" + } + ], + "messages": [ + { + "sender": "wxid_xxx", + "accountName": "用户名", + "timestamp": 1738713600000, + "type": 0, + "content": "消息内容" + } + ] +} +``` + +--- + +### 3. 获取会话列表 + +获取所有会话列表。 + +**请求** +``` +GET /api/v1/sessions +``` + +**参数** + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| `keyword` | string | ❌ | 搜索关键词,匹配会话名或 ID | +| `limit` | number | ❌ | 返回数量限制,默认 100 | + +**示例请求** +```bash +GET http://127.0.0.1:5031/api/v1/sessions + +GET http://127.0.0.1:5031/api/v1/sessions?keyword=工作群&limit=20 +``` + +**响应** +```json +{ + "success": true, + "count": 50, + "total": 100, + "sessions": [ + { + "username": "wxid_xxx", + "displayName": "用户名", + "lastMessage": "最后一条消息", + "lastTime": 1738713600000, + "unreadCount": 0 + } + ] +} +``` + +--- + +### 4. 获取联系人列表 + +获取所有联系人信息。 + +**请求** +``` +GET /api/v1/contacts +``` + +**参数** + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| `keyword` | string | ❌ | 搜索关键词 | +| `limit` | number | ❌ | 返回数量限制,默认 100 | + +**示例请求** +```bash +GET http://127.0.0.1:5031/api/v1/contacts + +GET http://127.0.0.1:5031/api/v1/contacts?keyword=张三 +``` + +**响应** +```json +{ + "success": true, + "count": 50, + "contacts": [ + { + "userName": "wxid_xxx", + "alias": "微信号", + "nickName": "昵称", + "remark": "备注名" + } + ] +} +``` + +--- + +## ChatLab 格式说明 + +ChatLab 是一种标准化的聊天记录交换格式,版本 0.0.2。 + +### 消息类型映射 + +| ChatLab Type | 值 | 说明 | +|--------------|-----|------| +| TEXT | 0 | 文本消息 | +| IMAGE | 1 | 图片 | +| VOICE | 2 | 语音 | +| VIDEO | 3 | 视频 | +| FILE | 4 | 文件 | +| EMOJI | 5 | 表情 | +| LINK | 7 | 链接 | +| LOCATION | 8 | 位置 | +| RED_PACKET | 20 | 红包 | +| TRANSFER | 21 | 转账 | +| CALL | 23 | 通话 | +| SYSTEM | 80 | 系统消息 | +| RECALL | 81 | 撤回消息 | +| OTHER | 99 | 其他 | + +--- + +## 使用示例 + +### PowerShell + +```powershell +# 健康检查 +Invoke-RestMethod http://127.0.0.1:5031/health + +# 获取会话列表 +Invoke-RestMethod http://127.0.0.1:5031/api/v1/sessions + +# 获取消息 +Invoke-RestMethod "http://127.0.0.1:5031/api/v1/messages?talker=wxid_xxx&limit=10" + +# 获取 ChatLab 格式 +Invoke-RestMethod "http://127.0.0.1:5031/api/v1/messages?talker=wxid_xxx&chatlab=1" | ConvertTo-Json -Depth 10 +``` + +### cURL + +```bash +# 健康检查 +curl http://127.0.0.1:5031/health + +# 获取会话列表 +curl http://127.0.0.1:5031/api/v1/sessions + +# 获取消息(ChatLab 格式) +curl "http://127.0.0.1:5031/api/v1/messages?talker=wxid_xxx&chatlab=1" +``` + +### Python + +```python +import requests + +BASE_URL = "http://127.0.0.1:5031" + +# 获取会话列表 +sessions = requests.get(f"{BASE_URL}/api/v1/sessions").json() +print(sessions) + +# 获取消息 +messages = requests.get(f"{BASE_URL}/api/v1/messages", params={ + "talker": "wxid_xxx", + "limit": 100, + "chatlab": 1 +}).json() +print(messages) +``` + +### JavaScript / Node.js + +```javascript +const BASE_URL = "http://127.0.0.1:5031"; + +// 获取会话列表 +const sessions = await fetch(`${BASE_URL}/api/v1/sessions`).then(r => r.json()); +console.log(sessions); + +// 获取消息(ChatLab 格式) +const messages = await fetch(`${BASE_URL}/api/v1/messages?talker=wxid_xxx&chatlab=1`) + .then(r => r.json()); +console.log(messages); +``` + +--- + +## 注意事项 + +1. API 仅监听本地地址 `127.0.0.1`,不对外网开放 +2. 需要先连接数据库才能查询数据 +3. 时间参数格式为 `YYYYMMDD`(如 20260205) +4. 支持 CORS,可从浏览器前端直接调用 diff --git a/package-lock.json b/package-lock.json index ec2c90a..41d3e16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "weflow", - "version": "1.5.2", + "version": "1.5.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "weflow", - "version": "1.5.2", + "version": "1.5.3", "hasInstallScript": true, "dependencies": { "better-sqlite3": "^12.5.0", diff --git a/package.json b/package.json index ec17f15..828afe1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "weflow", - "version": "1.5.2", + "version": "1.5.3", "description": "WeFlow", "main": "dist-electron/main.js", "author": "cc", diff --git a/src/pages/SettingsPage.scss b/src/pages/SettingsPage.scss index eaecd91..5b9503e 100644 --- a/src/pages/SettingsPage.scss +++ b/src/pages/SettingsPage.scss @@ -1191,6 +1191,109 @@ } } +// 通用弹窗覆盖层 +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + animation: fadeIn 0.2s ease; +} + +// API 警告弹窗 +.api-warning-modal { + width: 420px; + background: var(--bg-primary); + border-radius: 16px; + overflow: hidden; + box-shadow: 0 16px 48px rgba(0, 0, 0, 0.2); + animation: slideUp 0.25s ease; + + .modal-header { + display: flex; + align-items: center; + gap: 10px; + padding: 20px 24px; + border-bottom: 1px solid var(--border-color); + + svg { + color: var(--warning, #f59e0b); + } + + h3 { + margin: 0; + font-size: 16px; + font-weight: 600; + color: var(--text-primary); + } + } + + .modal-body { + padding: 20px 24px; + + .warning-text { + margin: 0 0 16px; + font-size: 14px; + color: var(--text-secondary); + line-height: 1.6; + } + + .warning-list { + display: flex; + flex-direction: column; + gap: 10px; + + .warning-item { + display: flex; + align-items: flex-start; + gap: 8px; + font-size: 13px; + color: var(--text-secondary); + line-height: 1.5; + + .bullet { + color: var(--warning, #f59e0b); + font-weight: bold; + } + } + } + } + + .modal-footer { + display: flex; + justify-content: flex-end; + gap: 10px; + padding: 16px 24px; + border-top: 1px solid var(--border-color); + background: var(--bg-secondary); + } +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} // 协议弹窗 .agreement-overlay { @@ -1857,6 +1960,23 @@ } } +// API 地址显示样式 +.api-url-display { + display: flex; + gap: 8px; + margin-top: 8px; + + input { + flex: 1; + font-family: 'SF Mono', 'Consolas', monospace; + font-size: 13px; + } + + .btn { + flex-shrink: 0; + } +} + // API 服务设置样式 .status-badge { display: inline-flex; diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index 469cdd0..5d24a7f 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -143,6 +143,7 @@ function SettingsPage() { const [httpApiPort, setHttpApiPort] = useState(5031) const [httpApiRunning, setHttpApiRunning] = useState(false) const [isTogglingApi, setIsTogglingApi] = useState(false) + const [showApiWarning, setShowApiWarning] = useState(false) const isClearingCache = isClearingAnalyticsCache || isClearingImageCache || isClearingAllCache @@ -1861,21 +1862,37 @@ function SettingsPage() { // HTTP API 服务控制 const handleToggleApi = async () => { if (isTogglingApi) return + + // 启动时显示警告弹窗 + if (!httpApiRunning) { + setShowApiWarning(true) + return + } + setIsTogglingApi(true) try { - if (httpApiRunning) { - await window.electronAPI.http.stop() - setHttpApiRunning(false) - showMessage('API 服务已停止', true) + await window.electronAPI.http.stop() + setHttpApiRunning(false) + showMessage('API 服务已停止', true) + } catch (e: any) { + showMessage(`操作失败: ${e}`, false) + } finally { + setIsTogglingApi(false) + } + } + + // 确认启动 API 服务 + const confirmStartApi = async () => { + setShowApiWarning(false) + setIsTogglingApi(true) + try { + const result = await window.electronAPI.http.start(httpApiPort) + if (result.success) { + setHttpApiRunning(true) + if (result.port) setHttpApiPort(result.port) + showMessage(`API 服务已启动,端口 ${result.port}`, true) } else { - const result = await window.electronAPI.http.start(httpApiPort) - if (result.success) { - setHttpApiRunning(true) - if (result.port) setHttpApiPort(result.port) - showMessage(`API 服务已启动,端口 ${result.port}`, true) - } else { - showMessage(`启动失败: ${result.error}`, false) - } + showMessage(`启动失败: ${result.error}`, false) } } catch (e: any) { showMessage(`操作失败: ${e}`, false) @@ -1893,113 +1910,93 @@ function SettingsPage() { const renderApiTab = () => (
-
-
- - 启用后可通过 HTTP 接口查询消息数据 -
-
- - {httpApiRunning ? '运行中' : '已停止'} - - -
+ /> + +
-
-
- API 服务监听的端口号 -
- setHttpApiPort(parseInt(e.target.value, 10) || 5031)} - disabled={httpApiRunning} - style={{ width: 120 }} - min={1024} - max={65535} - /> - - {httpApiRunning ? '停止服务后可修改端口' : '建议使用 1024-65535 之间的端口'} - -
+ API 服务监听的端口号(1024-65535) + setHttpApiPort(parseInt(e.target.value, 10) || 5031)} + disabled={httpApiRunning} + style={{ width: 120 }} + min={1024} + max={65535} + />
-
- -
- - 使用以下地址访问 API -
- http://127.0.0.1:{httpApiPort} - -
-
- -
- -
- - 支持的 API 接口列表 -
-
-
- GET - /api/v1/messages -
-

获取消息列表,支持 ChatLab 格式输出

-
- talker - 会话ID(必填) - limit - 数量限制 - start - 开始时间 (YYYYMMDD) - end - 结束时间 (YYYYMMDD) - chatlab=1 - 输出 ChatLab 格式 -
-
-
-
- GET - /api/v1/sessions -
-

获取会话列表

-
-
-
- GET - /api/v1/contacts -
-

获取联系人列表

-
-
-
- GET - /health -
-

健康检查

+ {httpApiRunning && ( +
+ + 使用以下地址访问 API +
+ +
-
+ )} -
- -
- -
- GET http://127.0.0.1:{httpApiPort}/api/v1/messages?talker=wxid_xxx&limit=100&chatlab=1 + {/* API 安全警告弹窗 */} + {showApiWarning && ( +
setShowApiWarning(false)}> +
e.stopPropagation()}> +
+ +

安全提示

+
+
+

启用 HTTP API 服务后,本机上的其他程序可通过接口访问您的聊天记录数据。

+
+
+ + 请确保您了解此功能的用途 +
+
+ + 不要在公共或不信任的网络环境下使用 +
+
+ + 此功能仅供高级用户或开发者使用 +
+
+
+
+ + +
+
-
+ )}
)