@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.
Files changed (69) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/README.md +11 -6
  3. package/adapters/{weixin-ilink.js → weixin-ilink/index.js} +6 -6
  4. package/adapters/{weixin-ilink.ts → weixin-ilink/index.ts} +9 -9
  5. package/commands/{endpoint/add/[id].js → weixin-ilink/endpoint/add/[id]/index.js} +1 -1
  6. package/commands/weixin-ilink/endpoint/add/[id]/index.ts +3 -0
  7. package/{lib/weixin-ilink-endpoint-commands.js → commands/weixin-ilink/endpoint/definition.js} +4 -3
  8. package/{src/weixin-ilink-endpoint-commands.ts → commands/weixin-ilink/endpoint/definition.ts} +3 -3
  9. package/commands/{endpoint/list.js → weixin-ilink/endpoint/list/index.js} +1 -1
  10. package/commands/weixin-ilink/endpoint/list/index.ts +3 -0
  11. package/commands/{endpoint/remove/[id].js → weixin-ilink/endpoint/remove/[id]/index.js} +1 -1
  12. package/commands/weixin-ilink/endpoint/remove/[id]/index.ts +3 -0
  13. package/lib/client.d.ts +5 -4
  14. package/lib/client.js +33 -16
  15. package/lib/context-store.d.ts +15 -1
  16. package/lib/context-store.js +93 -1
  17. package/lib/credentials.d.ts +15 -7
  18. package/lib/credentials.js +59 -42
  19. package/lib/endpoint.d.ts +9 -4
  20. package/lib/endpoint.js +41 -27
  21. package/lib/ilink-api.d.ts +6 -5
  22. package/lib/ilink-api.js +21 -14
  23. package/lib/ilink-config-cache.d.ts +3 -7
  24. package/lib/ilink-config-cache.js +13 -14
  25. package/lib/ilink-logger.d.ts +0 -1
  26. package/lib/ilink-logger.js +11 -25
  27. package/lib/ilink-meta.d.ts +13 -7
  28. package/lib/ilink-meta.js +24 -20
  29. package/lib/ilink-session-guard.d.ts +13 -14
  30. package/lib/ilink-session-guard.js +29 -42
  31. package/lib/index.d.ts +5 -2
  32. package/lib/index.js +4 -0
  33. package/lib/login.d.ts +4 -3
  34. package/lib/login.js +24 -21
  35. package/lib/outbound-media.js +1 -1
  36. package/lib/protocol.d.ts +7 -10
  37. package/lib/protocol.js +15 -15
  38. package/lib/types.d.ts +1 -1
  39. package/lib/weixin-ilink-runtime-state.js +1 -1
  40. package/lib/weixin-inbound.d.ts +3 -49
  41. package/lib/weixin-inbound.js +20 -183
  42. package/lib/weixin-send-media.js +5 -1
  43. package/lib/weixin-send.js +2 -0
  44. package/package.json +11 -11
  45. package/plugin.js +1 -1
  46. package/schema.json +32 -4
  47. package/src/client.ts +46 -14
  48. package/src/context-store.ts +101 -10
  49. package/src/credentials.ts +68 -45
  50. package/src/endpoint.ts +59 -38
  51. package/src/ilink-api.ts +26 -20
  52. package/src/ilink-config-cache.ts +17 -13
  53. package/src/ilink-logger.ts +11 -26
  54. package/src/ilink-meta.ts +31 -21
  55. package/src/ilink-session-guard.ts +29 -46
  56. package/src/index.ts +15 -2
  57. package/src/login.ts +32 -19
  58. package/src/outbound-media.ts +1 -1
  59. package/src/protocol.ts +20 -29
  60. package/src/types.ts +0 -1
  61. package/src/upload.test.ts +11 -8
  62. package/src/weixin-ilink-runtime-state.ts +1 -1
  63. package/src/weixin-inbound.ts +23 -215
  64. package/src/weixin-send-media.ts +5 -1
  65. package/src/weixin-send.ts +2 -0
  66. package/commands/endpoint/add/[id].ts +0 -3
  67. package/commands/endpoint/list.ts +0 -3
  68. package/commands/endpoint/remove/[id].ts +0 -3
  69. package/lib/weixin-ilink-endpoint-commands.d.ts +0 -1
@@ -1,48 +1,35 @@
1
- import { logger } from "./ilink-logger.js";
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 pauseUntilMap = new Map();
6
- /** Pause all inbound/outbound API calls for `accountId` for one hour. */
7
- export function pauseSession(accountId) {
8
- const until = Date.now() + SESSION_PAUSE_DURATION_MS;
9
- pauseUntilMap.set(accountId, until);
10
- logger.info(`session-guard: paused accountId=${accountId} until=${new Date(until).toISOString()} (${SESSION_PAUSE_DURATION_MS / 1000}s)`);
11
- }
12
- /** Returns `true` when the bot is still within its one-hour cooldown window. */
13
- export function isSessionPaused(accountId) {
14
- const until = pauseUntilMap.get(accountId);
15
- if (until === undefined)
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
- return true;
22
- }
23
- /** Milliseconds remaining until the pause expires (0 when not paused). */
24
- export function getRemainingPauseMs(accountId) {
25
- const until = pauseUntilMap.get(accountId);
26
- if (until === undefined)
27
- return 0;
28
- const remaining = until - Date.now();
29
- if (remaining <= 0) {
30
- pauseUntilMap.delete(accountId);
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
- return remaining;
34
- }
35
- /** Throw if the session is currently paused. Call before any API request. */
36
- export function assertSessionActive(accountId) {
37
- if (isSessionPaused(accountId)) {
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 WeixinIlinkAdapterConfig, type WeixinIlinkEndpointConfig, type WeixinInboundMediaPaths, type WeixinMessageWithMedia, type WeixinWireSegment, } from './protocol.js';
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 { WeixinIlinkCredentials } from './credentials.js';
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 { type WeixinIlinkCredentials } from './credentials.js';
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 (env / config / sidecar file / QR login).
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({ local_token_list: getLocalBotTokenList(), base_info: buildBaseInfo() }),
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 envToken = process.env.WEIXIN_ILINK_TOKEN?.trim() || config.botToken?.trim();
69
- if (envToken) {
70
- const stored = loadCredentials(config.id);
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: envToken,
75
+ botToken: configuredToken,
73
76
  ilinkUserId: stored?.ilinkUserId,
74
77
  ilinkBotId: stored?.ilinkBotId,
75
- baseUrl: config.baseUrl ?? stored?.baseUrl ?? DEFAULT_API_BASE_URL,
78
+ baseUrl: config.baseUrl,
76
79
  };
77
80
  }
78
- const stored = loadCredentials(config.id);
81
+ const stored = state.loadCredentials();
79
82
  if (stored?.botToken) {
80
83
  return {
81
84
  ...stored,
82
- baseUrl: config.baseUrl ?? stored.baseUrl ?? DEFAULT_API_BASE_URL,
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 ?? DEFAULT_API_BASE_URL;
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(config.id)?.botToken ?? '',
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(config.id, creds);
120
+ state.saveCredentials(creds);
118
121
  return creds;
119
122
  }
120
123
  if (status.status === 'expired') {
@@ -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/core';
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 { ConversationRef, Segment } from '@zhin.js/im-contract';
5
+ import { type ConversationRef, type Segment } from '@zhin.js/im-contract';
6
6
  import type { WeixinMessage } from './ilink-types.js';
7
- /** Plugin Runtime owner config (`plugins.<instanceKey>` / schema.json). */
8
- export interface WeixinIlinkAdapterConfig {
9
- readonly id?: string;
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
- /** Transitional: legacy root `endpoints[]` with `context: weixin-ilink`. */
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?: WeixinIlinkAdapterConfig): ResolvedWeixinIlinkConfig;
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 { pickCredential } from 'zhin.js/adapter';
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 entry = config.endpoints?.find((item) => item.context === 'weixin-ilink');
12
- const id = (typeof config.id === 'string' && config.id)
13
- || (typeof entry?.id === 'string' && entry.id)
14
- || process.env.WEIXIN_ILINK_BOT_NAME
15
- || 'weixin-ilink-bot';
16
- const botToken = pickCredential(typeof config.botToken === 'string' ? config.botToken.trim() : config.botToken, typeof entry?.botToken === 'string' ? entry.botToken.trim() : entry?.botToken, process.env.WEIXIN_ILINK_TOKEN?.trim());
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 ?? entry?.botAgent,
23
- baseUrl: config.baseUrl ?? entry?.baseUrl ?? DEFAULT_API_BASE_URL,
24
- cdnBaseUrl: config.cdnBaseUrl ?? entry?.cdnBaseUrl ?? DEFAULT_CDN_BASE_URL,
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, WeixinIlinkAdapterConfig, ResolvedWeixinIlinkConfig, } from './protocol.js';
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.endpoint` 命令共享(同一 owner generation)。
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');
@@ -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: "openclaw-weixin";
7
+ OriginatingChannel: 'openclaw-weixin';
37
8
  OriginatingTo: string;
38
9
  MessageSid: string;
39
10
  Timestamp?: number;
40
- Provider: "openclaw-weixin";
41
- ChatType: "direct";
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;