Merge pull request #835 from xunchahaha/dev

Dev
This commit is contained in:
xuncha
2026-04-25 00:58:34 +08:00
committed by GitHub
4 changed files with 778 additions and 37 deletions

View File

@@ -74,14 +74,14 @@ GET /api/v1/push/messages
- 需要先在设置页开启 `HTTP API 服务` - 需要先在设置页开启 `HTTP API 服务`
- 同时需要开启 `主动推送` - 同时需要开启 `主动推送`
- 响应类型为 `text/event-stream` - 响应类型为 `text/event-stream`
- 新消息事件名固定为 `message.new` - 事件名包含 `message.new``message.revoke`
- 建议接收端按 `messageKey` 去重 - 建议接收端按 `event + rawid` 去重
### 事件字段 ### 事件字段
- `event` - `event`
- `sessionId` - `sessionId`
- `messageKey` - `rawid`
- `avatarUrl` - `avatarUrl`
- `sourceName` - `sourceName`
- `groupName`(仅群聊) - `groupName`(仅群聊)
@@ -98,7 +98,14 @@ curl -N "http://127.0.0.1:5031/api/v1/push/messages?access_token=YOUR_TOKEN
```text ```text
event: message.new event: message.new
data: {"event":"message.new","sessionId":"xxx@chatroom","messageKey":"server:123456:1760000123:1760000123000:321:wxid_member:1","avatarUrl":"https://example.com/group.jpg","sourceName":"李四","groupName":"项目群","content":"[图片]","timestamp":1760000123} 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":"对方撤回了一条消息rawid1234567890123456789 内容为“你好”","timestamp":1760000180}
``` ```
--- ---

View File

@@ -290,7 +290,8 @@ class HttpService {
broadcastMessagePush(payload: Record<string, unknown>): void { broadcastMessagePush(payload: Record<string, unknown>): void {
if (!this.running) return if (!this.running) return
const eventId = this.nextMessagePushEventId() const eventId = this.nextMessagePushEventId()
const eventBody = `id: ${eventId}\nevent: message.new\ndata: ${JSON.stringify(payload)}\n\n` const eventName = this.getMessagePushEventName(payload)
const eventBody = `id: ${eventId}\nevent: ${eventName}\ndata: ${JSON.stringify(payload)}\n\n`
this.rememberMessagePushEvent(eventId, eventBody) this.rememberMessagePushEvent(eventId, eventBody)
if (this.messagePushClients.size === 0) return if (this.messagePushClients.size === 0) return
@@ -308,6 +309,11 @@ class HttpService {
} }
} }
private getMessagePushEventName(payload: Record<string, unknown>): string {
const eventName = String(payload?.event || '').trim()
return /^[a-z0-9._-]+$/i.test(eventName) ? eventName : 'message.new'
}
async autoStart(): Promise<void> { async autoStart(): Promise<void> {
const enabled = this.configService.get('httpApiEnabled') const enabled = this.configService.get('httpApiEnabled')
if (enabled) { if (enabled) {

File diff suppressed because it is too large Load Diff

View File

@@ -4120,16 +4120,16 @@ function SettingsPage({ onClose }: SettingsPageProps = {}) {
<div className="form-group"> <div className="form-group">
<label></label> <label></label>
<span className="form-hint">SSE `message.new` `avatarUrl/sourceName/content/timestamp` `groupName` `timestamp` Unix </span> <span className="form-hint">SSE `message.new` `message.revoke` `rawid/avatarUrl/sourceName/content/timestamp` `groupName` `timestamp` Unix </span>
<div className="api-docs"> <div className="api-docs">
<div className="api-item"> <div className="api-item">
<div className="api-endpoint"> <div className="api-endpoint">
<span className="method get">GET</span> <span className="method get">GET</span>
<code>{`http://${httpApiHost}:${httpApiPort}/api/v1/push/messages`}</code> <code>{`http://${httpApiHost}:${httpApiPort}/api/v1/push/messages`}</code>
</div> </div>
<p className="api-desc"> SSE `messageKey` </p> <p className="api-desc"> SSE `event + rawid` </p>
<div className="api-params"> <div className="api-params">
{['event', 'sessionId', 'sessionType', 'messageKey', 'avatarUrl', 'sourceName', 'groupName?', 'content', 'timestamp'].map((param) => ( {['event', 'sessionId', 'sessionType', 'rawid', 'avatarUrl', 'sourceName', 'groupName?', 'content', 'timestamp'].map((param) => (
<span key={param} className="param"> <span key={param} className="param">
<code>{param}</code> <code>{param}</code>
</span> </span>