@zhin.js/adapter-weixin-ilink 1.1.0 → 1.1.2
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 +74 -0
- package/README.md +11 -6
- package/adapters/{weixin-ilink.js → weixin-ilink/index.js} +6 -6
- package/adapters/{weixin-ilink.ts → weixin-ilink/index.ts} +9 -9
- package/commands/{endpoint/add/[id].js → weixin-ilink/endpoint/add/[id]/index.js} +1 -1
- package/commands/weixin-ilink/endpoint/add/[id]/index.ts +3 -0
- package/{lib/weixin-ilink-endpoint-commands.js → commands/weixin-ilink/endpoint/definition.js} +4 -3
- package/{src/weixin-ilink-endpoint-commands.ts → commands/weixin-ilink/endpoint/definition.ts} +3 -3
- package/commands/{endpoint/list.js → weixin-ilink/endpoint/list/index.js} +1 -1
- package/commands/weixin-ilink/endpoint/list/index.ts +3 -0
- package/commands/{endpoint/remove/[id].js → weixin-ilink/endpoint/remove/[id]/index.js} +1 -1
- package/commands/weixin-ilink/endpoint/remove/[id]/index.ts +3 -0
- package/lib/client.d.ts +5 -4
- package/lib/client.js +33 -16
- package/lib/context-store.d.ts +15 -1
- package/lib/context-store.js +93 -1
- package/lib/credentials.d.ts +15 -7
- package/lib/credentials.js +59 -42
- package/lib/endpoint.d.ts +9 -4
- package/lib/endpoint.js +41 -27
- package/lib/ilink-api.d.ts +6 -5
- package/lib/ilink-api.js +21 -14
- package/lib/ilink-config-cache.d.ts +3 -7
- package/lib/ilink-config-cache.js +13 -14
- package/lib/ilink-logger.d.ts +0 -1
- package/lib/ilink-logger.js +11 -25
- package/lib/ilink-meta.d.ts +13 -7
- package/lib/ilink-meta.js +24 -20
- package/lib/ilink-session-guard.d.ts +13 -14
- package/lib/ilink-session-guard.js +29 -42
- package/lib/index.d.ts +5 -2
- package/lib/index.js +4 -0
- package/lib/login.d.ts +4 -3
- package/lib/login.js +24 -21
- package/lib/outbound-media.js +1 -1
- package/lib/protocol.d.ts +7 -10
- package/lib/protocol.js +15 -15
- package/lib/types.d.ts +1 -1
- package/lib/weixin-ilink-runtime-state.js +1 -1
- package/lib/weixin-inbound.d.ts +3 -49
- package/lib/weixin-inbound.js +20 -183
- package/lib/weixin-send-media.js +5 -1
- package/lib/weixin-send.js +2 -0
- package/package.json +11 -11
- package/plugin.js +1 -1
- package/schema.json +32 -4
- package/src/client.ts +46 -14
- package/src/context-store.ts +101 -10
- package/src/credentials.ts +68 -45
- package/src/endpoint.ts +59 -38
- package/src/ilink-api.ts +26 -20
- package/src/ilink-config-cache.ts +17 -13
- package/src/ilink-logger.ts +11 -26
- package/src/ilink-meta.ts +31 -21
- package/src/ilink-session-guard.ts +29 -46
- package/src/index.ts +15 -2
- package/src/login.ts +32 -19
- package/src/outbound-media.ts +1 -1
- package/src/protocol.ts +20 -29
- package/src/types.ts +0 -1
- package/src/upload.test.ts +11 -8
- package/src/weixin-ilink-runtime-state.ts +1 -1
- package/src/weixin-inbound.ts +23 -215
- package/src/weixin-send-media.ts +5 -1
- package/src/weixin-send.ts +2 -0
- package/commands/endpoint/add/[id].ts +0 -3
- package/commands/endpoint/list.ts +0 -3
- package/commands/endpoint/remove/[id].ts +0 -3
- package/lib/weixin-ilink-endpoint-commands.d.ts +0 -1
|
@@ -1,48 +1,35 @@
|
|
|
1
|
-
import { logger } from
|
|
2
|
-
const SESSION_PAUSE_DURATION_MS = 60 * 60 * 1000;
|
|
3
|
-
/** Error code returned by the server when the bot session has expired. */
|
|
1
|
+
import { logger } from './ilink-logger.js';
|
|
4
2
|
export const SESSION_EXPIRED_ERRCODE = -14;
|
|
5
|
-
const
|
|
6
|
-
/**
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return false;
|
|
17
|
-
if (Date.now() >= until) {
|
|
18
|
-
pauseUntilMap.delete(accountId);
|
|
19
|
-
return false;
|
|
3
|
+
export const SESSION_PAUSE_DURATION_MS = 60 * 60 * 1000;
|
|
4
|
+
/** Endpoint-owned cooldown state for an authenticated iLink session. */
|
|
5
|
+
export class IlinkSessionGuard {
|
|
6
|
+
accountId;
|
|
7
|
+
now;
|
|
8
|
+
pauseDurationMs;
|
|
9
|
+
#pauseUntil = 0;
|
|
10
|
+
constructor(accountId, now = Date.now, pauseDurationMs = SESSION_PAUSE_DURATION_MS) {
|
|
11
|
+
this.accountId = accountId;
|
|
12
|
+
this.now = now;
|
|
13
|
+
this.pauseDurationMs = pauseDurationMs;
|
|
20
14
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
15
|
+
pause() {
|
|
16
|
+
this.#pauseUntil = this.now() + this.pauseDurationMs;
|
|
17
|
+
logger.info(`session-guard: paused accountId=${this.accountId} until=${new Date(this.#pauseUntil).toISOString()} (${this.pauseDurationMs / 1000}s)`);
|
|
18
|
+
}
|
|
19
|
+
get paused() {
|
|
20
|
+
return this.remainingMs > 0;
|
|
21
|
+
}
|
|
22
|
+
get remainingMs() {
|
|
23
|
+
const remaining = this.#pauseUntil - this.now();
|
|
24
|
+
if (remaining > 0)
|
|
25
|
+
return remaining;
|
|
26
|
+
this.#pauseUntil = 0;
|
|
31
27
|
return 0;
|
|
32
28
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const remainingMin = Math.ceil(getRemainingPauseMs(accountId) / 60_000);
|
|
39
|
-
throw new Error(`session paused for accountId=${accountId}, ${remainingMin} min remaining (errcode ${SESSION_EXPIRED_ERRCODE})`);
|
|
29
|
+
assertActive() {
|
|
30
|
+
const remaining = this.remainingMs;
|
|
31
|
+
if (remaining === 0)
|
|
32
|
+
return;
|
|
33
|
+
throw new Error(`session paused for accountId=${this.accountId}, ${Math.ceil(remaining / 60_000)} min remaining (errcode ${SESSION_EXPIRED_ERRCODE})`);
|
|
40
34
|
}
|
|
41
35
|
}
|
|
42
|
-
/**
|
|
43
|
-
* Reset internal state — only for tests.
|
|
44
|
-
* @internal
|
|
45
|
-
*/
|
|
46
|
-
export function _resetForTest() {
|
|
47
|
-
pauseUntilMap.clear();
|
|
48
|
-
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
export { formatInboundContent, formatOutboundSegments, inboundMessageId, resolveWeixinIlinkConfig, segmentLocalPath, sleep, type ResolvedWeixinIlinkConfig, type
|
|
1
|
+
export { formatInboundContent, formatOutboundSegments, inboundMessageId, resolveWeixinIlinkConfig, segmentLocalPath, sleep, type ResolvedWeixinIlinkConfig, type WeixinIlinkEndpointConfig, type WeixinInboundMediaPaths, type WeixinMessageWithMedia, type WeixinWireSegment, } from './protocol.js';
|
|
2
2
|
export type { WeixinMessage, MessageItem } from './ilink-types.js';
|
|
3
|
-
export type
|
|
3
|
+
export { WeixinIlinkStateStore, type WeixinIlinkCredentials, } from './credentials.js';
|
|
4
|
+
export { IlinkClientMetadata, sanitizeBotAgent, type IlinkClientMetadataOptions, } from './ilink-meta.js';
|
|
5
|
+
export { WeixinContextTokenStore } from './context-store.js';
|
|
6
|
+
export { IlinkSessionGuard, SESSION_EXPIRED_ERRCODE, SESSION_PAUSE_DURATION_MS, } from './ilink-session-guard.js';
|
|
4
7
|
export { WeixinIlinkClient, weixinIlinkClient, type WeixinIlinkClientEventMap, } from './client.js';
|
|
5
8
|
export { WeixinIlinkEndpoint, type WeixinIlinkEndpointOptions, type WeixinIlinkGetUpdates, type WeixinIlinkNotifyStart, type WeixinIlinkNotifyStop, type WeixinIlinkSendText, } from './endpoint.js';
|
package/lib/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
export { formatInboundContent, formatOutboundSegments, inboundMessageId, resolveWeixinIlinkConfig, segmentLocalPath, sleep, } from './protocol.js';
|
|
2
|
+
export { WeixinIlinkStateStore, } from './credentials.js';
|
|
3
|
+
export { IlinkClientMetadata, sanitizeBotAgent, } from './ilink-meta.js';
|
|
4
|
+
export { WeixinContextTokenStore } from './context-store.js';
|
|
5
|
+
export { IlinkSessionGuard, SESSION_EXPIRED_ERRCODE, SESSION_PAUSE_DURATION_MS, } from './ilink-session-guard.js';
|
|
2
6
|
export { WeixinIlinkClient, weixinIlinkClient, } from './client.js';
|
|
3
7
|
export { WeixinIlinkEndpoint, } from './endpoint.js';
|
package/lib/login.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { IlinkClientMetadata } from './ilink-meta.js';
|
|
2
|
+
import { type WeixinIlinkCredentials, type WeixinIlinkStateStore } from './credentials.js';
|
|
2
3
|
import type { ResolvedWeixinIlinkConfig } from './protocol.js';
|
|
3
|
-
export declare function resolveCredentials(config: ResolvedWeixinIlinkConfig, signal?: AbortSignal): Promise<WeixinIlinkCredentials>;
|
|
4
|
-
export declare function loginWithQr(config: ResolvedWeixinIlinkConfig, signal?: AbortSignal): Promise<WeixinIlinkCredentials>;
|
|
4
|
+
export declare function resolveCredentials(config: ResolvedWeixinIlinkConfig, metadata: IlinkClientMetadata, state: WeixinIlinkStateStore, signal?: AbortSignal): Promise<WeixinIlinkCredentials>;
|
|
5
|
+
export declare function loginWithQr(config: ResolvedWeixinIlinkConfig, metadata: IlinkClientMetadata, state: WeixinIlinkStateStore, signal?: AbortSignal): Promise<WeixinIlinkCredentials>;
|
package/lib/login.js
CHANGED
|
@@ -1,31 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Resolve iLink bot credentials (
|
|
2
|
+
* Resolve iLink bot credentials (config / endpoint state / QR login).
|
|
3
3
|
* No legacy Plugin / loginAssist dependency — QR URL is logged for manual scan.
|
|
4
4
|
*/
|
|
5
5
|
import { apiGetFetch, apiPostFetch } from './ilink-api.js';
|
|
6
|
-
import { buildBaseInfo, DEFAULT_API_BASE_URL } from './ilink-meta.js';
|
|
7
6
|
import { logger } from './ilink-logger.js';
|
|
8
|
-
import { loadCredentials, saveCredentials, } from './credentials.js';
|
|
9
7
|
const DEFAULT_ILINK_BOT_TYPE = '3';
|
|
10
8
|
const QR_LONG_POLL_TIMEOUT_MS = 35_000;
|
|
11
9
|
const LOGIN_TIMEOUT_MS = 480_000;
|
|
12
10
|
function getLocalBotTokenList() {
|
|
13
11
|
return [];
|
|
14
12
|
}
|
|
15
|
-
async function fetchQRCode(apiBaseUrl, botType) {
|
|
13
|
+
async function fetchQRCode(apiBaseUrl, botType, metadata) {
|
|
16
14
|
const rawText = await apiPostFetch({
|
|
17
15
|
baseUrl: apiBaseUrl,
|
|
16
|
+
metadata,
|
|
18
17
|
endpoint: `ilink/bot/get_bot_qrcode?bot_type=${encodeURIComponent(botType)}`,
|
|
19
|
-
body: JSON.stringify({
|
|
18
|
+
body: JSON.stringify({
|
|
19
|
+
local_token_list: getLocalBotTokenList(),
|
|
20
|
+
base_info: metadata.buildBaseInfo(),
|
|
21
|
+
}),
|
|
20
22
|
label: 'fetchQRCode',
|
|
21
23
|
});
|
|
22
24
|
return JSON.parse(rawText);
|
|
23
25
|
}
|
|
24
|
-
async function pollQRStatus(apiBaseUrl, qrcode, signal) {
|
|
26
|
+
async function pollQRStatus(apiBaseUrl, qrcode, metadata, signal) {
|
|
25
27
|
try {
|
|
26
28
|
const endpoint = `ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(qrcode)}`;
|
|
27
29
|
const rawText = await apiGetFetch({
|
|
28
30
|
baseUrl: apiBaseUrl,
|
|
31
|
+
metadata,
|
|
29
32
|
endpoint,
|
|
30
33
|
timeoutMs: QR_LONG_POLL_TIMEOUT_MS,
|
|
31
34
|
label: 'pollQRStatus',
|
|
@@ -64,29 +67,29 @@ function sleepWithAbort(ms, signal) {
|
|
|
64
67
|
signal?.addEventListener('abort', onAbort, { once: true });
|
|
65
68
|
});
|
|
66
69
|
}
|
|
67
|
-
export async function resolveCredentials(config, signal) {
|
|
68
|
-
const
|
|
69
|
-
if (
|
|
70
|
-
const stored = loadCredentials(
|
|
70
|
+
export async function resolveCredentials(config, metadata, state, signal) {
|
|
71
|
+
const configuredToken = config.botToken?.trim();
|
|
72
|
+
if (configuredToken) {
|
|
73
|
+
const stored = state.loadCredentials();
|
|
71
74
|
return {
|
|
72
|
-
botToken:
|
|
75
|
+
botToken: configuredToken,
|
|
73
76
|
ilinkUserId: stored?.ilinkUserId,
|
|
74
77
|
ilinkBotId: stored?.ilinkBotId,
|
|
75
|
-
baseUrl: config.baseUrl
|
|
78
|
+
baseUrl: config.baseUrl,
|
|
76
79
|
};
|
|
77
80
|
}
|
|
78
|
-
const stored = loadCredentials(
|
|
81
|
+
const stored = state.loadCredentials();
|
|
79
82
|
if (stored?.botToken) {
|
|
80
83
|
return {
|
|
81
84
|
...stored,
|
|
82
|
-
baseUrl: config.baseUrl
|
|
85
|
+
baseUrl: config.baseUrl,
|
|
83
86
|
};
|
|
84
87
|
}
|
|
85
|
-
return loginWithQr(config, signal);
|
|
88
|
+
return loginWithQr(config, metadata, state, signal);
|
|
86
89
|
}
|
|
87
|
-
export async function loginWithQr(config, signal) {
|
|
88
|
-
const apiBaseUrl = config.baseUrl
|
|
89
|
-
const qr = await fetchQRCode(apiBaseUrl, DEFAULT_ILINK_BOT_TYPE);
|
|
90
|
+
export async function loginWithQr(config, metadata, state, signal) {
|
|
91
|
+
const apiBaseUrl = config.baseUrl;
|
|
92
|
+
const qr = await fetchQRCode(apiBaseUrl, DEFAULT_ILINK_BOT_TYPE, metadata);
|
|
90
93
|
const qrcodeUrl = qr.qrcode_img_content || qr.qrcode;
|
|
91
94
|
logger.info(`weixin-ilink QR login for ${config.id}: scan with WeChat ClawBot entry — ${qrcodeUrl.slice(0, 120)}${qrcodeUrl.length > 120 ? '…' : ''}`);
|
|
92
95
|
const deadline = Date.now() + LOGIN_TIMEOUT_MS;
|
|
@@ -95,7 +98,7 @@ export async function loginWithQr(config, signal) {
|
|
|
95
98
|
// stop() 的 AbortSignal:不打满 8 分钟,立即退出登录循环
|
|
96
99
|
if (signal?.aborted)
|
|
97
100
|
throw abortError();
|
|
98
|
-
const status = await pollQRStatus(currentBaseUrl, qr.qrcode, signal);
|
|
101
|
+
const status = await pollQRStatus(currentBaseUrl, qr.qrcode, metadata, signal);
|
|
99
102
|
if (status.status === 'scaned_but_redirect' && status.redirect_host) {
|
|
100
103
|
currentBaseUrl = status.redirect_host.startsWith('http')
|
|
101
104
|
? status.redirect_host
|
|
@@ -106,7 +109,7 @@ export async function loginWithQr(config, signal) {
|
|
|
106
109
|
throw new Error('扫码成功但未返回 bot_token');
|
|
107
110
|
}
|
|
108
111
|
const creds = {
|
|
109
|
-
botToken: status.bot_token ?? loadCredentials(
|
|
112
|
+
botToken: status.bot_token ?? state.loadCredentials()?.botToken ?? '',
|
|
110
113
|
ilinkUserId: status.ilink_user_id,
|
|
111
114
|
ilinkBotId: status.ilink_endpoint_id,
|
|
112
115
|
baseUrl: status.baseurl ?? currentBaseUrl,
|
|
@@ -114,7 +117,7 @@ export async function loginWithQr(config, signal) {
|
|
|
114
117
|
if (!creds.botToken) {
|
|
115
118
|
throw new Error('binded_redirect 但本地无有效 bot_token');
|
|
116
119
|
}
|
|
117
|
-
saveCredentials(
|
|
120
|
+
state.saveCredentials(creds);
|
|
118
121
|
return creds;
|
|
119
122
|
}
|
|
120
123
|
if (status.status === 'expired') {
|
package/lib/outbound-media.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import crypto from 'node:crypto';
|
|
11
11
|
import fs from 'node:fs';
|
|
12
12
|
import path from 'node:path';
|
|
13
|
-
import { isMediaRef } from '@zhin.js/
|
|
13
|
+
import { isMediaRef } from '@zhin.js/im-contract';
|
|
14
14
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
15
15
|
import { downloadRemoteImageToTemp } from './upload.js';
|
|
16
16
|
import { getExtensionFromMime } from './mime.js';
|
package/lib/protocol.d.ts
CHANGED
|
@@ -2,20 +2,17 @@
|
|
|
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
|
|
5
|
+
import { type ConversationRef, type Segment } from '@zhin.js/im-contract';
|
|
6
6
|
import type { WeixinMessage } from './ilink-types.js';
|
|
7
|
-
/**
|
|
8
|
-
export interface
|
|
9
|
-
readonly id
|
|
7
|
+
/** One expanded Weixin iLink endpoint configuration produced by AdapterIndex. */
|
|
8
|
+
export interface WeixinIlinkEndpointConfig {
|
|
9
|
+
readonly id: string;
|
|
10
10
|
readonly botAgent?: string;
|
|
11
11
|
readonly baseUrl?: string;
|
|
12
12
|
readonly cdnBaseUrl?: string;
|
|
13
13
|
readonly longPollTimeoutMs?: number;
|
|
14
14
|
readonly botToken?: string;
|
|
15
|
-
|
|
16
|
-
readonly endpoints?: ReadonlyArray<Partial<ResolvedWeixinIlinkConfig> & {
|
|
17
|
-
readonly context?: string;
|
|
18
|
-
}>;
|
|
15
|
+
readonly dataDir?: string;
|
|
19
16
|
}
|
|
20
17
|
export interface ResolvedWeixinIlinkConfig {
|
|
21
18
|
readonly context: 'weixin-ilink';
|
|
@@ -25,8 +22,8 @@ export interface ResolvedWeixinIlinkConfig {
|
|
|
25
22
|
readonly cdnBaseUrl: string;
|
|
26
23
|
readonly longPollTimeoutMs: number;
|
|
27
24
|
readonly botToken?: string;
|
|
25
|
+
readonly dataDir: string;
|
|
28
26
|
}
|
|
29
|
-
export type WeixinIlinkEndpointConfig = ResolvedWeixinIlinkConfig;
|
|
30
27
|
export interface WeixinWireSegment {
|
|
31
28
|
readonly type: string;
|
|
32
29
|
readonly data?: Record<string, unknown>;
|
|
@@ -40,7 +37,7 @@ export interface WeixinInboundMediaPaths {
|
|
|
40
37
|
export type WeixinMessageWithMedia = WeixinMessage & {
|
|
41
38
|
readonly _media?: WeixinInboundMediaPaths;
|
|
42
39
|
};
|
|
43
|
-
export declare function resolveWeixinIlinkConfig(config
|
|
40
|
+
export declare function resolveWeixinIlinkConfig(config: WeixinIlinkEndpointConfig): ResolvedWeixinIlinkConfig;
|
|
44
41
|
/** Build inbound text for OutboundMessageService.receive (gateway owns reply routing). */
|
|
45
42
|
export declare function formatInboundContent(msg: WeixinMessageWithMedia): string;
|
|
46
43
|
export declare function weixinInboundSegments(msg: WeixinMessageWithMedia): readonly Segment[];
|
package/lib/protocol.js
CHANGED
|
@@ -2,30 +2,30 @@
|
|
|
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 {
|
|
6
|
-
import { isMediaRef } from '@zhin.js/core';
|
|
5
|
+
import { isMediaRef } from '@zhin.js/im-contract';
|
|
7
6
|
import { bodyFromItemList } from './weixin-inbound.js';
|
|
8
7
|
import { DEFAULT_API_BASE_URL, DEFAULT_CDN_BASE_URL } from './ilink-meta.js';
|
|
9
8
|
const DEFAULT_LONG_POLL_TIMEOUT_MS = 35_000;
|
|
10
|
-
export function resolveWeixinIlinkConfig(config
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
export function resolveWeixinIlinkConfig(config) {
|
|
10
|
+
const id = typeof config.id === 'string' ? config.id.trim() : '';
|
|
11
|
+
if (!/^[A-Za-z0-9_.-]+$/u.test(id)) {
|
|
12
|
+
throw new TypeError('Weixin iLink endpoint id must use letters, numbers, dot, underscore, or hyphen');
|
|
13
|
+
}
|
|
14
|
+
const botToken = typeof config.botToken === 'string'
|
|
15
|
+
? config.botToken.trim() || undefined
|
|
16
|
+
: undefined;
|
|
17
|
+
const dataDir = config.dataDir?.trim() || 'data/weixin-ilink';
|
|
17
18
|
// No throw here: missing botToken is resolved later by resolveCredentials()
|
|
18
19
|
// (sidecar credentials file / QR login) during endpoint.start().
|
|
19
20
|
return {
|
|
20
21
|
context: 'weixin-ilink',
|
|
21
22
|
id,
|
|
22
|
-
botAgent: config.botAgent
|
|
23
|
-
baseUrl: config.baseUrl ??
|
|
24
|
-
cdnBaseUrl: config.cdnBaseUrl ??
|
|
25
|
-
longPollTimeoutMs: config.longPollTimeoutMs
|
|
26
|
-
?? entry?.longPollTimeoutMs
|
|
27
|
-
?? DEFAULT_LONG_POLL_TIMEOUT_MS,
|
|
23
|
+
botAgent: config.botAgent,
|
|
24
|
+
baseUrl: config.baseUrl ?? DEFAULT_API_BASE_URL,
|
|
25
|
+
cdnBaseUrl: config.cdnBaseUrl ?? DEFAULT_CDN_BASE_URL,
|
|
26
|
+
longPollTimeoutMs: config.longPollTimeoutMs ?? DEFAULT_LONG_POLL_TIMEOUT_MS,
|
|
28
27
|
botToken,
|
|
28
|
+
dataDir,
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
31
|
/** Build inbound text for OutboundMessageService.receive (gateway owns reply routing). */
|
package/lib/types.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export type { WeixinIlinkEndpointConfig,
|
|
1
|
+
export type { WeixinIlinkEndpointConfig, ResolvedWeixinIlinkConfig, } from './protocol.js';
|
|
2
2
|
export type { WeixinMessage, MessageItem } from './ilink-types.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Weixin iLink 插件实例的运行时状态:adapter create() 注册的 endpoint 列表。
|
|
3
|
-
* 由 plugin.ts setup() provide,adapter create 与 `weixin-ilink
|
|
3
|
+
* 由 plugin.ts setup() provide,adapter create 与 `weixin-ilink endpoint` 命令共享(同一 owner generation)。
|
|
4
4
|
*/
|
|
5
5
|
import { defineEndpointRuntimeStateToken } from 'zhin.js/adapter';
|
|
6
6
|
export const weixinIlinkRuntimeStateToken = defineEndpointRuntimeStateToken('weixin-ilink');
|
package/lib/weixin-inbound.d.ts
CHANGED
|
@@ -1,78 +1,32 @@
|
|
|
1
1
|
import { type WeixinMessage, type MessageItem } from './ilink-types.js';
|
|
2
|
-
/** 立即落盘所有待写(endpoint stop / 测试用)。 */
|
|
3
|
-
export declare function flushContextTokenPersist(accountId?: string): void;
|
|
4
|
-
/**
|
|
5
|
-
* Restore persisted context tokens for an account into the in-memory map.
|
|
6
|
-
* Called once during gateway startAccount to survive restarts.
|
|
7
|
-
*/
|
|
8
|
-
export declare function restoreContextTokens(accountId: string): void;
|
|
9
|
-
/** Remove all context tokens for a given account (memory + disk). */
|
|
10
|
-
export declare function clearContextTokensForAccount(accountId: string): void;
|
|
11
|
-
/** Store a context token for a given account+user pair (memory + debounced disk). */
|
|
12
|
-
export declare function setContextToken(accountId: string, userId: string, token: string): void;
|
|
13
|
-
/** Retrieve the cached context token for a given account+user pair. */
|
|
14
|
-
export declare function getContextToken(accountId: string, userId: string): string | undefined;
|
|
15
|
-
/**
|
|
16
|
-
* List all peer userIds that hold a context token for the given account
|
|
17
|
-
* (memory map, already merged with the disk-backed file on restore).
|
|
18
|
-
* Personal WeChat has no contact/profile API; this is the only peer catalog.
|
|
19
|
-
*/
|
|
20
|
-
export declare function listContextTokenUserIds(accountId: string): string[];
|
|
21
|
-
/**
|
|
22
|
-
* Find all accountIds that have an active contextToken for the given userId.
|
|
23
|
-
* Used to infer the sending bot account from the recipient address when
|
|
24
|
-
* accountId is not explicitly provided (e.g. cron delivery).
|
|
25
|
-
*
|
|
26
|
-
* Returns all matching accountIds (not just the first) so the caller can
|
|
27
|
-
* detect ambiguity when multiple accounts have sessions with the same user.
|
|
28
|
-
*/
|
|
29
|
-
export declare function findAccountIdsByContextToken(accountIds: string[], userId: string): string[];
|
|
30
|
-
/** Inbound context passed to the OpenClaw core pipeline (matches MsgContext shape). */
|
|
31
2
|
export type WeixinMsgContext = {
|
|
32
3
|
Body: string;
|
|
33
4
|
From: string;
|
|
34
5
|
To: string;
|
|
35
6
|
AccountId: string;
|
|
36
|
-
OriginatingChannel:
|
|
7
|
+
OriginatingChannel: 'openclaw-weixin';
|
|
37
8
|
OriginatingTo: string;
|
|
38
9
|
MessageSid: string;
|
|
39
10
|
Timestamp?: number;
|
|
40
|
-
Provider:
|
|
41
|
-
ChatType:
|
|
42
|
-
/** Set by monitor after resolveAgentRoute so dispatchReplyFromConfig uses the correct session. */
|
|
11
|
+
Provider: 'openclaw-weixin';
|
|
12
|
+
ChatType: 'direct';
|
|
43
13
|
SessionKey?: string;
|
|
44
14
|
context_token?: string;
|
|
45
15
|
MediaUrl?: string;
|
|
46
16
|
MediaPath?: string;
|
|
47
17
|
MediaType?: string;
|
|
48
|
-
/** Raw message body for framework command authorization. */
|
|
49
18
|
CommandBody?: string;
|
|
50
|
-
/** Whether the sender is authorized to execute slash commands. */
|
|
51
19
|
CommandAuthorized?: boolean;
|
|
52
20
|
};
|
|
53
|
-
/** Returns true if the message item is a media type (image, video, file, or voice). */
|
|
54
21
|
export declare function isMediaItem(item: MessageItem): boolean;
|
|
55
22
|
export declare function bodyFromItemList(itemList?: MessageItem[]): string;
|
|
56
23
|
export type WeixinInboundMediaOpts = {
|
|
57
|
-
/** Local path to decrypted image file. */
|
|
58
24
|
decryptedPicPath?: string;
|
|
59
|
-
/** Local path to transcoded/raw voice file (.wav or .silk). */
|
|
60
25
|
decryptedVoicePath?: string;
|
|
61
|
-
/** MIME type for the voice file (e.g. "audio/wav" or "audio/silk"). */
|
|
62
26
|
voiceMediaType?: string;
|
|
63
|
-
/** Local path to decrypted file attachment. */
|
|
64
27
|
decryptedFilePath?: string;
|
|
65
|
-
/** MIME type for the file attachment (guessed from file_name). */
|
|
66
28
|
fileMediaType?: string;
|
|
67
|
-
/** Local path to decrypted video file. */
|
|
68
29
|
decryptedVideoPath?: string;
|
|
69
30
|
};
|
|
70
|
-
/**
|
|
71
|
-
* Convert a WeixinMessage from getUpdates to the inbound MsgContext for the core pipeline.
|
|
72
|
-
* Media: only pass MediaPath (local file, after CDN download + decrypt).
|
|
73
|
-
* We never pass MediaUrl — the upstream CDN URL is encrypted/auth-only.
|
|
74
|
-
* Priority when multiple media types present: image > video > file > voice.
|
|
75
|
-
*/
|
|
76
31
|
export declare function weixinMessageToMsgContext(msg: WeixinMessage, accountId: string, opts?: WeixinInboundMediaOpts): WeixinMsgContext;
|
|
77
|
-
/** Extract the context_token from an inbound WeixinMsgContext. */
|
|
78
32
|
export declare function getContextTokenFromMsgContext(ctx: WeixinMsgContext): string | undefined;
|