@zhin.js/adapter-weixin-ilink 5.0.12 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/adapters/weixin-ilink.js +3 -1
- package/adapters/weixin-ilink.ts +3 -1
- package/lib/endpoint.d.ts +4 -2
- package/lib/endpoint.js +7 -1
- package/lib/protocol.d.ts +5 -1
- package/lib/protocol.js +19 -11
- package/lib/weixin-inbound.js +1 -19
- package/package.json +9 -9
- package/src/endpoint.ts +14 -1
- package/src/protocol.ts +20 -12
- package/src/weixin-inbound.ts +1 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# @zhin.js/adapter-weixin-ilink
|
|
2
2
|
|
|
3
|
+
## 6.0.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f2c532f: Expose exact per-Endpoint message operations through one validated Adapter capability model, route Core control calls through declared active capabilities, and connect existing recall, edit, reaction, and typing implementations across platform adapters.
|
|
8
|
+
- Updated dependencies [f2c532f]
|
|
9
|
+
- @zhin.js/adapter@1.2.0
|
|
10
|
+
- @zhin.js/core@1.5.13
|
|
11
|
+
- zhin.js@6.0.13
|
|
12
|
+
|
|
13
|
+
## 5.0.13
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 5969c5b: Add SideEventGateway so adapters forward notice/request/system into HandlerIndex. HandlerContext now exposes only generation-safe capabilities and prompt ports; live Endpoint escape hatches are removed.
|
|
18
|
+
- 3b3ec03: Emit canonical reply and media segments instead of eager quote text and media placeholders.
|
|
19
|
+
- Updated dependencies [5969c5b]
|
|
20
|
+
- Updated dependencies [5969c5b]
|
|
21
|
+
- Updated dependencies [5969c5b]
|
|
22
|
+
- Updated dependencies [974772e]
|
|
23
|
+
- Updated dependencies [5969c5b]
|
|
24
|
+
- Updated dependencies [2f786bd]
|
|
25
|
+
- Updated dependencies [1312ca0]
|
|
26
|
+
- @zhin.js/im-contract@1.0.4
|
|
27
|
+
- @zhin.js/core@1.5.12
|
|
28
|
+
- @zhin.js/adapter@1.1.11
|
|
29
|
+
- @zhin.js/command@1.0.15
|
|
30
|
+
- zhin.js@6.0.12
|
|
31
|
+
|
|
3
32
|
## 5.0.12
|
|
4
33
|
|
|
5
34
|
### Patch Changes
|
package/adapters/weixin-ilink.js
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
* Convention entry: discover `adapters/weixin-ilink.ts` → defineAdapter.
|
|
4
4
|
*/
|
|
5
5
|
import { defineAdapter } from 'zhin.js/adapter';
|
|
6
|
-
import { messageGatewayToken } from '@zhin.js/core/runtime';
|
|
6
|
+
import { messageGatewayToken, sideEventGatewayToken } from '@zhin.js/core/runtime';
|
|
7
7
|
import { WeixinIlinkEndpoint } from "../lib/endpoint.js";
|
|
8
8
|
import { resolveWeixinIlinkConfig, } from "../lib/protocol.js";
|
|
9
9
|
import { weixinIlinkRuntimeStateToken } from "../lib/weixin-ilink-runtime-state.js";
|
|
10
10
|
export { WeixinIlinkEndpoint } from "../lib/endpoint.js";
|
|
11
11
|
export default defineAdapter({
|
|
12
12
|
capabilities: ['inbound', 'outbound'],
|
|
13
|
+
operations: ['typing'],
|
|
13
14
|
// 所有媒体(url 下载 / base64 落盘 / 本地 path)统一物化后走 CDN 上传(sendWeixinMediaFile),
|
|
14
15
|
// 无直发面;微信无卡片交互面,交互段降级纯文本。
|
|
15
16
|
segments: {
|
|
@@ -27,6 +28,7 @@ export default defineAdapter({
|
|
|
27
28
|
return new WeixinIlinkEndpoint({
|
|
28
29
|
id: context.id,
|
|
29
30
|
gateway: context.use(messageGatewayToken),
|
|
31
|
+
sideEvents: context.use(sideEventGatewayToken),
|
|
30
32
|
config,
|
|
31
33
|
});
|
|
32
34
|
},
|
package/adapters/weixin-ilink.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Convention entry: discover `adapters/weixin-ilink.ts` → defineAdapter.
|
|
3
3
|
*/
|
|
4
4
|
import { defineAdapter } from 'zhin.js/adapter';
|
|
5
|
-
import { messageGatewayToken } from '@zhin.js/core/runtime';
|
|
5
|
+
import { messageGatewayToken, sideEventGatewayToken } from '@zhin.js/core/runtime';
|
|
6
6
|
import { WeixinIlinkEndpoint } from '../src/endpoint.js';
|
|
7
7
|
import {
|
|
8
8
|
resolveWeixinIlinkConfig,
|
|
@@ -21,6 +21,7 @@ export type {
|
|
|
21
21
|
|
|
22
22
|
export default defineAdapter<WeixinIlinkAdapterConfig>({
|
|
23
23
|
capabilities: ['inbound', 'outbound'],
|
|
24
|
+
operations: ['typing'],
|
|
24
25
|
// 所有媒体(url 下载 / base64 落盘 / 本地 path)统一物化后走 CDN 上传(sendWeixinMediaFile),
|
|
25
26
|
// 无直发面;微信无卡片交互面,交互段降级纯文本。
|
|
26
27
|
segments: {
|
|
@@ -38,6 +39,7 @@ export default defineAdapter<WeixinIlinkAdapterConfig>({
|
|
|
38
39
|
return new WeixinIlinkEndpoint({
|
|
39
40
|
id: context.id,
|
|
40
41
|
gateway: context.use(messageGatewayToken),
|
|
42
|
+
sideEvents: context.use(sideEventGatewayToken),
|
|
41
43
|
config,
|
|
42
44
|
});
|
|
43
45
|
},
|
package/lib/endpoint.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from 'zhin.js/adapter';
|
|
2
|
-
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
1
|
+
import type { EndpointControl, EndpointInstance, EndpointManagement, EndpointSendRequest } from 'zhin.js/adapter';
|
|
2
|
+
import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
|
|
3
3
|
import type { CapabilityId } from 'zhin.js';
|
|
4
4
|
import { getUpdates, notifyStart, notifyStop } from './ilink-api.js';
|
|
5
5
|
import { type WeixinIlinkCredentials } from './credentials.js';
|
|
@@ -12,6 +12,7 @@ export type WeixinIlinkGetUpdates = typeof getUpdates;
|
|
|
12
12
|
export interface WeixinIlinkEndpointOptions {
|
|
13
13
|
readonly id: CapabilityId;
|
|
14
14
|
readonly gateway: MessageGateway;
|
|
15
|
+
readonly sideEvents?: SideEventGateway;
|
|
15
16
|
readonly config: ResolvedWeixinIlinkConfig;
|
|
16
17
|
readonly resolveCredentials?: (config: ResolvedWeixinIlinkConfig, signal?: AbortSignal) => Promise<WeixinIlinkCredentials>;
|
|
17
18
|
/** Test / internal: override network side effects. */
|
|
@@ -23,6 +24,7 @@ export interface WeixinIlinkEndpointOptions {
|
|
|
23
24
|
export declare class WeixinIlinkEndpoint implements EndpointInstance {
|
|
24
25
|
#private;
|
|
25
26
|
readonly management: EndpointManagement;
|
|
27
|
+
readonly control: EndpointControl;
|
|
26
28
|
constructor(options: WeixinIlinkEndpointOptions);
|
|
27
29
|
get hasCredentials(): boolean;
|
|
28
30
|
/** Live endpoint 名(context token 按 account 名分桶,management 推导对端用)。 */
|
package/lib/endpoint.js
CHANGED
|
@@ -18,7 +18,7 @@ import { sendWeixinMediaFile } from './weixin-send-media.js';
|
|
|
18
18
|
import { materializeOutboundMedia } from './outbound-media.js';
|
|
19
19
|
import { MessageItemType } from './ilink-types.js';
|
|
20
20
|
import { getExtensionFromMime, sniffMimeFromBuffer } from './mime.js';
|
|
21
|
-
import { formatInboundContent, formatOutboundSegments, inboundMessageId, segmentLocalPath, sleep, weixinIlinkInboundConversation, } from './protocol.js';
|
|
21
|
+
import { formatInboundContent, weixinInboundSegments, weixinReplyTo, formatOutboundSegments, inboundMessageId, segmentLocalPath, sleep, weixinIlinkInboundConversation, } from './protocol.js';
|
|
22
22
|
const MAX_CONSECUTIVE_FAILURES = 3;
|
|
23
23
|
const BACKOFF_DELAY_MS = 30_000;
|
|
24
24
|
const RETRY_DELAY_MS = 2_000;
|
|
@@ -43,6 +43,9 @@ export class WeixinIlinkEndpoint {
|
|
|
43
43
|
#open = false;
|
|
44
44
|
#started = false;
|
|
45
45
|
management = createWeixinIlinkEndpointManagement(this);
|
|
46
|
+
control = Object.freeze({
|
|
47
|
+
typing: (conversation, active) => this.sendTypingToUser(conversation.id, active === false ? 2 : 1),
|
|
48
|
+
});
|
|
46
49
|
constructor(options) {
|
|
47
50
|
this.#logger = getAdapterLogger('weixin-ilink', options.config.id);
|
|
48
51
|
this.#options = options;
|
|
@@ -218,10 +221,13 @@ export class WeixinIlinkEndpoint {
|
|
|
218
221
|
setContextToken(this.#options.config.id, userId, msg.context_token);
|
|
219
222
|
}
|
|
220
223
|
const conversation = weixinIlinkInboundConversation(String(this.#options.id), userId);
|
|
224
|
+
const replyTo = weixinReplyTo(msg);
|
|
221
225
|
void this.#options.gateway.receive({
|
|
222
226
|
conversation,
|
|
223
227
|
message: { conversation, id: inboundMessageId(msg) },
|
|
224
228
|
content: formatInboundContent(msg),
|
|
229
|
+
segments: weixinInboundSegments(msg),
|
|
230
|
+
...(replyTo ? { replyTo } : {}),
|
|
225
231
|
sender: { id: userId },
|
|
226
232
|
endpointId: this.#options.config.id,
|
|
227
233
|
metadata: Object.freeze({
|
package/lib/protocol.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Weixin iLink protocol helpers (no legacy Adapter/Endpoint / segment-mapper).
|
|
3
3
|
* Canonicalization is owned by gateway/core before endpoint.send.
|
|
4
4
|
*/
|
|
5
|
-
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
5
|
+
import type { ConversationRef, Segment } from '@zhin.js/im-contract';
|
|
6
6
|
import type { WeixinMessage } from './ilink-types.js';
|
|
7
7
|
/** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
|
|
8
8
|
export interface WeixinIlinkAdapterConfig {
|
|
@@ -43,6 +43,10 @@ export type WeixinMessageWithMedia = WeixinMessage & {
|
|
|
43
43
|
export declare function resolveWeixinIlinkConfig(config?: WeixinIlinkAdapterConfig): ResolvedWeixinIlinkConfig;
|
|
44
44
|
/** Build inbound text for MessageGateway.receive (gateway owns reply routing). */
|
|
45
45
|
export declare function formatInboundContent(msg: WeixinMessageWithMedia): string;
|
|
46
|
+
export declare function weixinInboundSegments(msg: WeixinMessageWithMedia): readonly Segment[];
|
|
47
|
+
export declare function weixinReplyTo(msg: WeixinMessageWithMedia): Readonly<{
|
|
48
|
+
id: string;
|
|
49
|
+
}> | undefined;
|
|
46
50
|
export declare function inboundMessageId(msg: WeixinMessage): string;
|
|
47
51
|
/**
|
|
48
52
|
* 入站归一化 → ConversationRef:个人微信无群/频道概念,全部会话都是
|
package/lib/protocol.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { pickCredential } from 'zhin.js/adapter';
|
|
6
6
|
import { isMediaRef } from '@zhin.js/core';
|
|
7
|
-
import { bodyFromItemList
|
|
7
|
+
import { bodyFromItemList } from './weixin-inbound.js';
|
|
8
8
|
import { DEFAULT_API_BASE_URL, DEFAULT_CDN_BASE_URL } from './ilink-meta.js';
|
|
9
9
|
const DEFAULT_LONG_POLL_TIMEOUT_MS = 35_000;
|
|
10
10
|
export function resolveWeixinIlinkConfig(config = {}) {
|
|
@@ -30,27 +30,35 @@ export function resolveWeixinIlinkConfig(config = {}) {
|
|
|
30
30
|
}
|
|
31
31
|
/** Build inbound text for MessageGateway.receive (gateway owns reply routing). */
|
|
32
32
|
export function formatInboundContent(msg) {
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
return bodyFromItemList(msg.item_list).trim();
|
|
34
|
+
}
|
|
35
|
+
export function weixinInboundSegments(msg) {
|
|
36
|
+
const segments = [];
|
|
37
|
+
const text = formatInboundContent(msg);
|
|
35
38
|
if (text)
|
|
36
|
-
|
|
39
|
+
segments.push({ type: 'text', data: { text } });
|
|
37
40
|
const media = msg._media;
|
|
38
41
|
if (media?.decryptedPicPath) {
|
|
39
|
-
|
|
42
|
+
segments.push({ type: 'image', data: { media: { kind: 'path', value: media.decryptedPicPath } } });
|
|
40
43
|
}
|
|
41
44
|
else if (media?.decryptedVideoPath) {
|
|
42
|
-
|
|
45
|
+
segments.push({ type: 'video', data: { media: { kind: 'path', value: media.decryptedVideoPath } } });
|
|
43
46
|
}
|
|
44
47
|
else if (media?.decryptedFilePath) {
|
|
45
|
-
|
|
48
|
+
segments.push({ type: 'file', data: { media: { kind: 'path', value: media.decryptedFilePath } } });
|
|
46
49
|
}
|
|
47
50
|
else if (media?.decryptedVoicePath) {
|
|
48
|
-
|
|
51
|
+
segments.push({ type: 'audio', data: { media: { kind: 'path', value: media.decryptedVoicePath } } });
|
|
49
52
|
}
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
return Object.freeze(segments);
|
|
54
|
+
}
|
|
55
|
+
export function weixinReplyTo(msg) {
|
|
56
|
+
for (const item of msg.item_list ?? []) {
|
|
57
|
+
const id = item.ref_msg?.message_item?.msg_id;
|
|
58
|
+
if (id && String(id).trim())
|
|
59
|
+
return Object.freeze({ id: String(id).trim() });
|
|
52
60
|
}
|
|
53
|
-
return
|
|
61
|
+
return undefined;
|
|
54
62
|
}
|
|
55
63
|
export function inboundMessageId(msg) {
|
|
56
64
|
return String(msg.message_id ?? msg.client_id ?? msg.seq ?? Date.now());
|
package/lib/weixin-inbound.js
CHANGED
|
@@ -168,25 +168,7 @@ export function bodyFromItemList(itemList) {
|
|
|
168
168
|
return "";
|
|
169
169
|
for (const item of itemList) {
|
|
170
170
|
if (item.type === MessageItemType.TEXT && item.text_item?.text != null) {
|
|
171
|
-
|
|
172
|
-
const ref = item.ref_msg;
|
|
173
|
-
if (!ref)
|
|
174
|
-
return text;
|
|
175
|
-
// Quoted media is passed as MediaPath; only include the current text as body.
|
|
176
|
-
if (ref.message_item && isMediaItem(ref.message_item))
|
|
177
|
-
return text;
|
|
178
|
-
// Build quoted context from both title and message_item content.
|
|
179
|
-
const parts = [];
|
|
180
|
-
if (ref.title)
|
|
181
|
-
parts.push(ref.title);
|
|
182
|
-
if (ref.message_item) {
|
|
183
|
-
const refBody = bodyFromItemList([ref.message_item]);
|
|
184
|
-
if (refBody)
|
|
185
|
-
parts.push(refBody);
|
|
186
|
-
}
|
|
187
|
-
if (!parts.length)
|
|
188
|
-
return text;
|
|
189
|
-
return `[引用: ${parts.join(" | ")}]\n${text}`;
|
|
171
|
+
return String(item.text_item.text);
|
|
190
172
|
}
|
|
191
173
|
// 语音转文字:如果语音消息有 text 字段,直接使用文字内容
|
|
192
174
|
if (item.type === MessageItemType.VOICE && item.voice_item?.text) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-weixin-ilink",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Zhin.js Weixin iLink (ClawBot) adapter for Plugin Runtime (long-poll)",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -48,16 +48,16 @@
|
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@zhin.js/adapter": "1.
|
|
52
|
-
"@zhin.js/core": "1.5.
|
|
53
|
-
"@zhin.js/im-contract": "1.0.
|
|
51
|
+
"@zhin.js/adapter": "1.2.0",
|
|
52
|
+
"@zhin.js/core": "1.5.13",
|
|
53
|
+
"@zhin.js/im-contract": "1.0.4",
|
|
54
54
|
"@zhin.js/logger": "1.0.76"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"@zhin.js/adapter": "1.
|
|
58
|
-
"@zhin.js/command": "1.0.
|
|
59
|
-
"@zhin.js/core": "1.5.
|
|
60
|
-
"zhin.js": "6.0.
|
|
57
|
+
"@zhin.js/adapter": "1.2.0",
|
|
58
|
+
"@zhin.js/command": "1.0.15",
|
|
59
|
+
"@zhin.js/core": "1.5.13",
|
|
60
|
+
"zhin.js": "6.0.13"
|
|
61
61
|
},
|
|
62
62
|
"peerDependenciesMeta": {
|
|
63
63
|
"@zhin.js/command": {
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"@types/node": "^26.1.2",
|
|
72
72
|
"typescript": "^6.0.3",
|
|
73
73
|
"vitest": "^4.1.10",
|
|
74
|
-
"zhin.js": "6.0.
|
|
74
|
+
"zhin.js": "6.0.13"
|
|
75
75
|
},
|
|
76
76
|
"ilink_appid": "bot",
|
|
77
77
|
"engines": {
|
package/src/endpoint.ts
CHANGED
|
@@ -6,11 +6,12 @@ import fs from 'node:fs';
|
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import type {
|
|
8
8
|
EndpointFriend,
|
|
9
|
+
EndpointControl,
|
|
9
10
|
EndpointInstance,
|
|
10
11
|
EndpointManagement,
|
|
11
12
|
EndpointSendRequest,
|
|
12
13
|
} from 'zhin.js/adapter';
|
|
13
|
-
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
14
|
+
import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
|
|
14
15
|
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
15
16
|
import type { CapabilityId } from 'zhin.js';
|
|
16
17
|
import { getUpdates, notifyStart, notifyStop, sendTyping } from './ilink-api.js';
|
|
@@ -45,6 +46,8 @@ import { getExtensionFromMime, sniffMimeFromBuffer } from './mime.js';
|
|
|
45
46
|
import type { WeixinInboundMediaOpts } from './weixin-inbound.js';
|
|
46
47
|
import {
|
|
47
48
|
formatInboundContent,
|
|
49
|
+
weixinInboundSegments,
|
|
50
|
+
weixinReplyTo,
|
|
48
51
|
formatOutboundSegments,
|
|
49
52
|
inboundMessageId,
|
|
50
53
|
segmentLocalPath,
|
|
@@ -71,6 +74,7 @@ export type WeixinIlinkGetUpdates = typeof getUpdates;
|
|
|
71
74
|
export interface WeixinIlinkEndpointOptions {
|
|
72
75
|
readonly id: CapabilityId;
|
|
73
76
|
readonly gateway: MessageGateway;
|
|
77
|
+
readonly sideEvents?: SideEventGateway;
|
|
74
78
|
readonly config: ResolvedWeixinIlinkConfig;
|
|
75
79
|
readonly resolveCredentials?: (
|
|
76
80
|
config: ResolvedWeixinIlinkConfig,
|
|
@@ -104,6 +108,12 @@ export class WeixinIlinkEndpoint implements EndpointInstance {
|
|
|
104
108
|
#open = false;
|
|
105
109
|
#started = false;
|
|
106
110
|
readonly management: EndpointManagement = createWeixinIlinkEndpointManagement(this);
|
|
111
|
+
readonly control: EndpointControl = Object.freeze<EndpointControl>({
|
|
112
|
+
typing: (conversation, active) => this.sendTypingToUser(
|
|
113
|
+
conversation.id,
|
|
114
|
+
active === false ? 2 : 1,
|
|
115
|
+
),
|
|
116
|
+
});
|
|
107
117
|
|
|
108
118
|
constructor(options: WeixinIlinkEndpointOptions) {
|
|
109
119
|
this.#logger = getAdapterLogger('weixin-ilink', options.config.id);
|
|
@@ -297,10 +307,13 @@ export class WeixinIlinkEndpoint implements EndpointInstance {
|
|
|
297
307
|
setContextToken(this.#options.config.id, userId, msg.context_token);
|
|
298
308
|
}
|
|
299
309
|
const conversation = weixinIlinkInboundConversation(String(this.#options.id), userId);
|
|
310
|
+
const replyTo = weixinReplyTo(msg);
|
|
300
311
|
void this.#options.gateway.receive({
|
|
301
312
|
conversation,
|
|
302
313
|
message: { conversation, id: inboundMessageId(msg) },
|
|
303
314
|
content: formatInboundContent(msg),
|
|
315
|
+
segments: weixinInboundSegments(msg),
|
|
316
|
+
...(replyTo ? { replyTo } : {}),
|
|
304
317
|
sender: { id: userId },
|
|
305
318
|
endpointId: this.#options.config.id,
|
|
306
319
|
metadata: Object.freeze({
|
package/src/protocol.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
import { pickCredential } from 'zhin.js/adapter';
|
|
7
7
|
import { isMediaRef } from '@zhin.js/core';
|
|
8
|
-
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
9
|
-
import { bodyFromItemList
|
|
8
|
+
import type { ConversationRef, Segment } from '@zhin.js/im-contract';
|
|
9
|
+
import { bodyFromItemList } from './weixin-inbound.js';
|
|
10
10
|
import { DEFAULT_API_BASE_URL, DEFAULT_CDN_BASE_URL } from './ilink-meta.js';
|
|
11
11
|
import type { WeixinMessage } from './ilink-types.js';
|
|
12
12
|
|
|
@@ -84,24 +84,32 @@ export function resolveWeixinIlinkConfig(
|
|
|
84
84
|
|
|
85
85
|
/** Build inbound text for MessageGateway.receive (gateway owns reply routing). */
|
|
86
86
|
export function formatInboundContent(msg: WeixinMessageWithMedia): string {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (text) parts.push(text);
|
|
87
|
+
return bodyFromItemList(msg.item_list).trim();
|
|
88
|
+
}
|
|
90
89
|
|
|
90
|
+
export function weixinInboundSegments(msg: WeixinMessageWithMedia): readonly Segment[] {
|
|
91
|
+
const segments: Segment[] = [];
|
|
92
|
+
const text = formatInboundContent(msg);
|
|
93
|
+
if (text) segments.push({ type: 'text', data: { text } });
|
|
91
94
|
const media = msg._media;
|
|
92
95
|
if (media?.decryptedPicPath) {
|
|
93
|
-
|
|
96
|
+
segments.push({ type: 'image', data: { media: { kind: 'path', value: media.decryptedPicPath } } });
|
|
94
97
|
} else if (media?.decryptedVideoPath) {
|
|
95
|
-
|
|
98
|
+
segments.push({ type: 'video', data: { media: { kind: 'path', value: media.decryptedVideoPath } } });
|
|
96
99
|
} else if (media?.decryptedFilePath) {
|
|
97
|
-
|
|
100
|
+
segments.push({ type: 'file', data: { media: { kind: 'path', value: media.decryptedFilePath } } });
|
|
98
101
|
} else if (media?.decryptedVoicePath) {
|
|
99
|
-
|
|
100
|
-
} else if (!text && msg.item_list?.some((item) => isMediaItem(item))) {
|
|
101
|
-
parts.push('[媒体消息]');
|
|
102
|
+
segments.push({ type: 'audio', data: { media: { kind: 'path', value: media.decryptedVoicePath } } });
|
|
102
103
|
}
|
|
104
|
+
return Object.freeze(segments);
|
|
105
|
+
}
|
|
103
106
|
|
|
104
|
-
|
|
107
|
+
export function weixinReplyTo(msg: WeixinMessageWithMedia): Readonly<{ id: string }> | undefined {
|
|
108
|
+
for (const item of msg.item_list ?? []) {
|
|
109
|
+
const id = item.ref_msg?.message_item?.msg_id;
|
|
110
|
+
if (id && String(id).trim()) return Object.freeze({ id: String(id).trim() });
|
|
111
|
+
}
|
|
112
|
+
return undefined;
|
|
105
113
|
}
|
|
106
114
|
|
|
107
115
|
export function inboundMessageId(msg: WeixinMessage): string {
|
package/src/weixin-inbound.ts
CHANGED
|
@@ -211,20 +211,7 @@ export function bodyFromItemList(itemList?: MessageItem[]): string {
|
|
|
211
211
|
if (!itemList?.length) return "";
|
|
212
212
|
for (const item of itemList) {
|
|
213
213
|
if (item.type === MessageItemType.TEXT && item.text_item?.text != null) {
|
|
214
|
-
|
|
215
|
-
const ref = item.ref_msg;
|
|
216
|
-
if (!ref) return text;
|
|
217
|
-
// Quoted media is passed as MediaPath; only include the current text as body.
|
|
218
|
-
if (ref.message_item && isMediaItem(ref.message_item)) return text;
|
|
219
|
-
// Build quoted context from both title and message_item content.
|
|
220
|
-
const parts: string[] = [];
|
|
221
|
-
if (ref.title) parts.push(ref.title);
|
|
222
|
-
if (ref.message_item) {
|
|
223
|
-
const refBody = bodyFromItemList([ref.message_item]);
|
|
224
|
-
if (refBody) parts.push(refBody);
|
|
225
|
-
}
|
|
226
|
-
if (!parts.length) return text;
|
|
227
|
-
return `[引用: ${parts.join(" | ")}]\n${text}`;
|
|
214
|
+
return String(item.text_item.text);
|
|
228
215
|
}
|
|
229
216
|
// 语音转文字:如果语音消息有 text 字段,直接使用文字内容
|
|
230
217
|
if (item.type === MessageItemType.VOICE && item.voice_item?.text) {
|