@onlyne/im-wechat 0.0.0-stage → 3.2.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/LICENSE +21 -0
- package/README.md +46 -2
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +223 -0
- package/dist/index.js.map +1 -0
- package/dist/normalize.d.ts +14 -0
- package/dist/normalize.d.ts.map +1 -0
- package/dist/normalize.js +89 -0
- package/dist/normalize.js.map +1 -0
- package/dist/port.d.ts +139 -0
- package/dist/port.d.ts.map +1 -0
- package/dist/port.js +168 -0
- package/dist/port.js.map +1 -0
- package/dist/send.d.ts +24 -0
- package/dist/send.d.ts.map +1 -0
- package/dist/send.js +64 -0
- package/dist/send.js.map +1 -0
- package/package.json +38 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dbydd
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,47 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @onlyne/im-wechat
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The WeChat connector for the Onlyne IM gateway. It uses the official iLink (ClawBot) bot API:
|
|
4
|
+
long-poll for inbound messages, send text with the conversation context token, and report typing.
|
|
5
|
+
|
|
6
|
+
## Credential
|
|
7
|
+
|
|
8
|
+
The constructor accepts a `WECHAT_ILINK_BOT_TOKEN` value from explicit configuration, a
|
|
9
|
+
credential file, or the environment. `WECHAT_ILINK_BASE_URL` may override the platform base URL.
|
|
10
|
+
The token comes from the ClawBot QR login flow, which the node does not drive itself.
|
|
11
|
+
|
|
12
|
+
## Transport
|
|
13
|
+
|
|
14
|
+
The connector long-polls `getupdates` and holds the platform continuation cursor in memory for
|
|
15
|
+
the life of the poll loop. It advances that cursor only after the gateway stored every message in
|
|
16
|
+
the batch. A listener failure leaves the cursor unchanged, and the platform replay is safe
|
|
17
|
+
because resource delivery deduplicates by platform message id.
|
|
18
|
+
|
|
19
|
+
## Supported
|
|
20
|
+
|
|
21
|
+
| Direction | Capability |
|
|
22
|
+
|---|---|
|
|
23
|
+
| Inbound | Text, voice transcript, image, file, video, and audio handles |
|
|
24
|
+
| Outbound | Text with the inbound context token, typing |
|
|
25
|
+
|
|
26
|
+
Quoting, attachments, recall, and group administration are not implemented. A send without an
|
|
27
|
+
inbound context token is refused by the platform, and that refusal is reported verbatim. An
|
|
28
|
+
expired session (`ret = -14`) moves the connector to a paused state with an onboarding note and
|
|
29
|
+
keeps retrying.
|
|
30
|
+
|
|
31
|
+
## Example
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { createWechatConnector } from '@onlyne/im-wechat';
|
|
35
|
+
|
|
36
|
+
const connector = createWechatConnector({ credentialFile: './credentials/wechat' });
|
|
37
|
+
await connector.start();
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Sources
|
|
41
|
+
|
|
42
|
+
- WeChat iLink openapi: <https://developers.weixin.qq.com/doc/aispeech/knowledge/openapi/Clawbotrelated.html>
|
|
43
|
+
- Wire protocol: <https://github.com/Tencent/openclaw-weixin/blob/main/docs/protocol.md>
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
MIT.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Json } from '@onlyne/lib';
|
|
2
|
+
import { type Connector, type SendOutcome } from '@onlyne/im-core';
|
|
3
|
+
import { type WechatPort } from './port.js';
|
|
4
|
+
export type { WechatConfigResponse, WechatMessage, WechatMessageItem, WechatMediaRef, WechatOutboundMessage, WechatPort, WechatPortOptions, WechatSendResponse, WechatUpdatesResponse, } from './port.js';
|
|
5
|
+
export { WECHAT_DEFAULT_BASE_URL, WechatPortError, createWechatPort } from './port.js';
|
|
6
|
+
export declare const WECHAT_CREDENTIAL_VARIABLES: readonly ["WECHAT_ILINK_BOT_TOKEN"];
|
|
7
|
+
/** Platform operations the connector implements beyond `send`; a resource lists them. */
|
|
8
|
+
export declare const WECHAT_OPERATIONS: readonly ["typing"];
|
|
9
|
+
/** The host-side status view; `Connector` deliberately does not carry it, the gateway duck-types it. */
|
|
10
|
+
export interface WechatStatus {
|
|
11
|
+
state: string;
|
|
12
|
+
connected: boolean;
|
|
13
|
+
reconnects: number;
|
|
14
|
+
rate_limits: number;
|
|
15
|
+
last_event_at?: string;
|
|
16
|
+
onboarding?: string[];
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface WechatConnectorOptions {
|
|
20
|
+
/** The bot token from the ClawBot QR login; wins over file and environment. */
|
|
21
|
+
credential?: string;
|
|
22
|
+
/** Path to a file holding the token; `$VAR` references resolve from the environment. */
|
|
23
|
+
credentialFile?: string;
|
|
24
|
+
env?: Record<string, string | undefined>;
|
|
25
|
+
readFile?: (path: string) => Promise<string>;
|
|
26
|
+
baseUrl?: string;
|
|
27
|
+
/** The HTTP boundary; production leaves it unset and one is built from the credential. */
|
|
28
|
+
port?: WechatPort;
|
|
29
|
+
log?: (message: string, fields?: Record<string, unknown>) => void;
|
|
30
|
+
now?: () => number;
|
|
31
|
+
sleep?: (ms: number) => Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
export interface WechatConnector extends Connector {
|
|
34
|
+
readonly operations: readonly string[];
|
|
35
|
+
validate(operation: string, data: Json): void;
|
|
36
|
+
operate(operation: string, data: Json): Promise<SendOutcome>;
|
|
37
|
+
status(): WechatStatus;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The iLink connector. Inbound is a long poll of `getupdates`: the cursor is the platform's
|
|
41
|
+
* `get_updates_buf`, held in memory only, and it moves forward at exactly one place — after every
|
|
42
|
+
* message of a batch was accepted by every listener. A rejected event leaves the cursor alone, so
|
|
43
|
+
* the next poll asks for the same batch again and the message is not lost.
|
|
44
|
+
*/
|
|
45
|
+
export declare function createWechatConnector(options?: WechatConnectorOptions): WechatConnector;
|
|
46
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,EAAqB,KAAK,SAAS,EAAgB,KAAK,WAAW,EAAoB,MAAM,iBAAiB,CAAC;AACtH,OAAO,EAEL,KAAK,UAAU,EAChB,MAAM,WAAW,CAAC;AAInB,YAAY,EACV,oBAAoB,EAAE,aAAa,EAAE,iBAAiB,EAAE,cAAc,EAAE,qBAAqB,EAAE,UAAU,EACzG,iBAAiB,EAAE,kBAAkB,EAAE,qBAAqB,GAC7D,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,uBAAuB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAEvF,eAAO,MAAM,2BAA2B,qCAAsC,CAAC;AAC/E,yFAAyF;AACzF,eAAO,MAAM,iBAAiB,qBAAsB,CAAC;AASrD,wGAAwG;AACxG,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,sBAAsB;IACrC,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wFAAwF;IACxF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0FAA0F;IAC1F,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAClE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAChD,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAC9C,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAC7D,MAAM,IAAI,YAAY,CAAC;CACxB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,sBAA2B,GAAG,eAAe,CA+M3F"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { readFile as nodeReadFile } from 'node:fs/promises';
|
|
2
|
+
import { setTimeout as sleepFor } from 'node:timers/promises';
|
|
3
|
+
import { OnlyneError } from '@onlyne/lib';
|
|
4
|
+
import { resolveCredential } from '@onlyne/im-core';
|
|
5
|
+
import { WechatPortError, createWechatPort, detailOf, } from './port.js';
|
|
6
|
+
import { normalizeMessage } from './normalize.js';
|
|
7
|
+
import { conversationTarget, outcomeOf, sendMessage } from './send.js';
|
|
8
|
+
export { WECHAT_DEFAULT_BASE_URL, WechatPortError, createWechatPort } from './port.js';
|
|
9
|
+
export const WECHAT_CREDENTIAL_VARIABLES = ['WECHAT_ILINK_BOT_TOKEN'];
|
|
10
|
+
/** Platform operations the connector implements beyond `send`; a resource lists them. */
|
|
11
|
+
export const WECHAT_OPERATIONS = ['typing'];
|
|
12
|
+
const PLATFORM = 'wechat';
|
|
13
|
+
const BASE_BACKOFF_MS = 1_000;
|
|
14
|
+
const MAX_BACKOFF_MS = 30_000;
|
|
15
|
+
/** A dead session needs a QR scan, so retrying it fast only burns the operator's rate budget. */
|
|
16
|
+
const SESSION_BACKOFF_MS = 60_000;
|
|
17
|
+
const SESSION_ONBOARDING = 'WECHAT_ILINK_BOT_TOKEN (re-scan the ClawBot QR to refresh the session)';
|
|
18
|
+
/**
|
|
19
|
+
* The iLink connector. Inbound is a long poll of `getupdates`: the cursor is the platform's
|
|
20
|
+
* `get_updates_buf`, held in memory only, and it moves forward at exactly one place — after every
|
|
21
|
+
* message of a batch was accepted by every listener. A rejected event leaves the cursor alone, so
|
|
22
|
+
* the next poll asks for the same batch again and the message is not lost.
|
|
23
|
+
*/
|
|
24
|
+
export function createWechatConnector(options = {}) {
|
|
25
|
+
const sleep = options.sleep ?? ((ms) => sleepFor(ms));
|
|
26
|
+
const clock = options.now ?? (() => Date.now());
|
|
27
|
+
let port = options.port;
|
|
28
|
+
let controller;
|
|
29
|
+
let state = 'stopped';
|
|
30
|
+
let connected = false;
|
|
31
|
+
let reconnects = 0;
|
|
32
|
+
let lastEventAt;
|
|
33
|
+
let onboarding = [];
|
|
34
|
+
let failureText;
|
|
35
|
+
const listeners = [];
|
|
36
|
+
/** One chain per conversation: the endpoint's `receive` calls land in arrival order. */
|
|
37
|
+
const queues = new Map();
|
|
38
|
+
function emit(event) {
|
|
39
|
+
const previous = queues.get(event.conversation) ?? Promise.resolve();
|
|
40
|
+
const next = previous.catch(() => undefined).then(async () => {
|
|
41
|
+
for (const listener of listeners)
|
|
42
|
+
await listener(event);
|
|
43
|
+
});
|
|
44
|
+
queues.set(event.conversation, next);
|
|
45
|
+
const settle = () => {
|
|
46
|
+
if (queues.get(event.conversation) === next)
|
|
47
|
+
queues.delete(event.conversation);
|
|
48
|
+
};
|
|
49
|
+
void next.then(settle, settle);
|
|
50
|
+
return next;
|
|
51
|
+
}
|
|
52
|
+
async function poll(client, signal) {
|
|
53
|
+
let cursor = '';
|
|
54
|
+
let failures = 0;
|
|
55
|
+
while (!signal.aborted) {
|
|
56
|
+
let batch;
|
|
57
|
+
try {
|
|
58
|
+
batch = await client.getUpdates(cursor, signal);
|
|
59
|
+
}
|
|
60
|
+
catch (failure) {
|
|
61
|
+
if (signal.aborted)
|
|
62
|
+
return;
|
|
63
|
+
failures += 1;
|
|
64
|
+
reconnects += 1;
|
|
65
|
+
failureText = detailOf(failure);
|
|
66
|
+
if (failure instanceof WechatPortError && failure.sessionExpired) {
|
|
67
|
+
state = 'paused';
|
|
68
|
+
connected = false;
|
|
69
|
+
onboarding = [SESSION_ONBOARDING];
|
|
70
|
+
options.log?.('wechat: the iLink session expired; polling is paused until the ClawBot QR is scanned again', { error: failureText });
|
|
71
|
+
await sleep(SESSION_BACKOFF_MS);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
state = 'error';
|
|
75
|
+
connected = false;
|
|
76
|
+
options.log?.('wechat: getupdates failed; retrying', { error: failureText, attempt: failures });
|
|
77
|
+
await sleep(Math.min(BASE_BACKOFF_MS * 2 ** (failures - 1), MAX_BACKOFF_MS));
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (signal.aborted)
|
|
81
|
+
return;
|
|
82
|
+
try {
|
|
83
|
+
for (const message of batch.msgs ?? []) {
|
|
84
|
+
const event = normalizeMessage(message, clock);
|
|
85
|
+
if (event === undefined)
|
|
86
|
+
continue;
|
|
87
|
+
await emit(event);
|
|
88
|
+
lastEventAt = event.at;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (failure) {
|
|
92
|
+
failures += 1;
|
|
93
|
+
reconnects += 1;
|
|
94
|
+
failureText = detailOf(failure);
|
|
95
|
+
options.log?.('wechat: a listener rejected an event; the batch will be redelivered', { error: failureText, attempt: failures });
|
|
96
|
+
await sleep(Math.min(BASE_BACKOFF_MS * 2 ** (failures - 1), MAX_BACKOFF_MS));
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (signal.aborted)
|
|
100
|
+
return;
|
|
101
|
+
// The one place the cursor moves: the whole batch was accepted, so the platform may drop it.
|
|
102
|
+
cursor = batch.get_updates_buf ?? cursor;
|
|
103
|
+
failures = 0;
|
|
104
|
+
connected = true;
|
|
105
|
+
state = 'polling';
|
|
106
|
+
failureText = undefined;
|
|
107
|
+
onboarding = [];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** The operation's target user id, validated before any platform call. */
|
|
111
|
+
function typingTarget(operation, data) {
|
|
112
|
+
if (!WECHAT_OPERATIONS.includes(operation)) {
|
|
113
|
+
throw new OnlyneError('invalid_request', `The WeChat connector does not implement ${operation}.`);
|
|
114
|
+
}
|
|
115
|
+
const raw = data !== null && typeof data === 'object' && !Array.isArray(data) ? data : undefined;
|
|
116
|
+
const conversation = raw?.conversation;
|
|
117
|
+
const target = typeof conversation === 'string' ? conversationTarget(conversation) : undefined;
|
|
118
|
+
if (target === undefined) {
|
|
119
|
+
throw new OnlyneError('invalid_request', 'typing names conversation as user:<id> or group:<id>.');
|
|
120
|
+
}
|
|
121
|
+
return target.id;
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
platform: PLATFORM,
|
|
125
|
+
operations: WECHAT_OPERATIONS,
|
|
126
|
+
async start() {
|
|
127
|
+
if (controller !== undefined)
|
|
128
|
+
return;
|
|
129
|
+
if (port === undefined) {
|
|
130
|
+
const env = options.env ?? process.env;
|
|
131
|
+
const resolved = await resolveCredential({
|
|
132
|
+
value: options.credential,
|
|
133
|
+
file: options.credentialFile,
|
|
134
|
+
variables: [...WECHAT_CREDENTIAL_VARIABLES],
|
|
135
|
+
}, options.readFile ?? (path => nodeReadFile(path, 'utf8')), env);
|
|
136
|
+
const token = resolved.value?.trim();
|
|
137
|
+
if (token === undefined || token.length === 0) {
|
|
138
|
+
state = 'onboarding';
|
|
139
|
+
connected = false;
|
|
140
|
+
// An unreadable credential file is reported verbatim; a plain absence names the field.
|
|
141
|
+
onboarding = [
|
|
142
|
+
resolved.problem !== undefined && (options.credential !== undefined || options.credentialFile !== undefined)
|
|
143
|
+
? resolved.problem
|
|
144
|
+
: WECHAT_CREDENTIAL_VARIABLES[0],
|
|
145
|
+
];
|
|
146
|
+
options.log?.('wechat: no bot token; the channel is onboarding', { onboarding: onboarding.join(', ') });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const baseUrl = options.baseUrl ?? env.WECHAT_ILINK_BASE_URL;
|
|
150
|
+
port = createWechatPort({ token, ...(baseUrl === undefined ? {} : { baseUrl }) });
|
|
151
|
+
}
|
|
152
|
+
const client = port;
|
|
153
|
+
state = 'connecting';
|
|
154
|
+
connected = false;
|
|
155
|
+
failureText = undefined;
|
|
156
|
+
void client.notifyStart().catch(failure => options.log?.('wechat: notifystart failed', { error: detailOf(failure) }));
|
|
157
|
+
const run = new AbortController();
|
|
158
|
+
controller = run;
|
|
159
|
+
void poll(client, run.signal).catch(failure => {
|
|
160
|
+
state = 'error';
|
|
161
|
+
connected = false;
|
|
162
|
+
failureText = detailOf(failure);
|
|
163
|
+
options.log?.('wechat: the poll loop stopped unexpectedly', { error: failureText });
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
onEvent(listener) {
|
|
167
|
+
listeners.push(listener);
|
|
168
|
+
},
|
|
169
|
+
async send(request) {
|
|
170
|
+
const client = port;
|
|
171
|
+
if (client === undefined) {
|
|
172
|
+
return { state: 'unknown', error: 'The WeChat connector is not connected; no iLink session is available.' };
|
|
173
|
+
}
|
|
174
|
+
return sendMessage(client, request);
|
|
175
|
+
},
|
|
176
|
+
validate(operation, data) {
|
|
177
|
+
typingTarget(operation, data);
|
|
178
|
+
},
|
|
179
|
+
async operate(operation, data) {
|
|
180
|
+
const userId = typingTarget(operation, data);
|
|
181
|
+
const client = port;
|
|
182
|
+
if (client === undefined) {
|
|
183
|
+
return { state: 'unknown', error: 'The WeChat connector is not connected; no iLink session is available.' };
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
const config = await client.getConfig(userId);
|
|
187
|
+
const ticket = config.typing_ticket;
|
|
188
|
+
if (typeof ticket !== 'string' || ticket.length === 0) {
|
|
189
|
+
return { state: 'unknown', error: 'The platform call failed: getconfig answered without a typing_ticket.' };
|
|
190
|
+
}
|
|
191
|
+
await client.sendTyping(userId, ticket, 1);
|
|
192
|
+
return { state: 'committed' };
|
|
193
|
+
}
|
|
194
|
+
catch (failure) {
|
|
195
|
+
return outcomeOf(failure);
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
async stop() {
|
|
199
|
+
const run = controller;
|
|
200
|
+
controller = undefined;
|
|
201
|
+
run?.abort();
|
|
202
|
+
listeners.length = 0;
|
|
203
|
+
queues.clear();
|
|
204
|
+
state = 'stopped';
|
|
205
|
+
connected = false;
|
|
206
|
+
if (port !== undefined) {
|
|
207
|
+
await port.notifyStop().catch(failure => options.log?.('wechat: notifystop failed', { error: detailOf(failure) }));
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
status() {
|
|
211
|
+
return {
|
|
212
|
+
state,
|
|
213
|
+
connected,
|
|
214
|
+
reconnects,
|
|
215
|
+
rate_limits: port?.rateLimits() ?? 0,
|
|
216
|
+
...(lastEventAt === undefined ? {} : { last_event_at: lastEventAt }),
|
|
217
|
+
...(onboarding.length === 0 ? {} : { onboarding: [...onboarding] }),
|
|
218
|
+
...(failureText === undefined ? {} : { error: failureText }),
|
|
219
|
+
};
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ImEvent } from '@onlyne/im-core';
|
|
2
|
+
import type { WechatMessage } from './port.js';
|
|
3
|
+
/**
|
|
4
|
+
* One `WeixinMessage` from `getupdates`. `message_type === 2` is the bot's own outbound echo and
|
|
5
|
+
* never becomes an inbound event; a message with no sender or no id cannot be deduplicated, so it
|
|
6
|
+
* is dropped rather than given a synthetic id that would collide on the next poll.
|
|
7
|
+
*
|
|
8
|
+
* The conversation coordinate is `group:<group_id>` when the message names a group, else
|
|
9
|
+
* `user:<from_user_id>`. The inbound `context_token` rides in `ref_handle` as `ctx:<token>`: the
|
|
10
|
+
* bridge hands it back as `reply_to` on the answer to this event, and iLink wants it verbatim on
|
|
11
|
+
* the send. `message_handle` stays the platform message id.
|
|
12
|
+
*/
|
|
13
|
+
export declare function normalizeMessage(message: WechatMessage, now: () => number): ImEvent | undefined;
|
|
14
|
+
//# sourceMappingURL=normalize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../src/normalize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,OAAO,EAAW,MAAM,iBAAiB,CAAC;AAEtE,OAAO,KAAK,EAAkB,aAAa,EAAqB,MAAM,WAAW,CAAC;AAYlF;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,MAAM,GAAG,OAAO,GAAG,SAAS,CA+C/F"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const PLATFORM = 'wechat';
|
|
2
|
+
/** Item type to attachment kind: 2 image, 3 voice, 4 file, 5 video. */
|
|
3
|
+
const ATTACHMENT_KINDS = {
|
|
4
|
+
2: 'image',
|
|
5
|
+
3: 'audio',
|
|
6
|
+
4: 'file',
|
|
7
|
+
5: 'video',
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* One `WeixinMessage` from `getupdates`. `message_type === 2` is the bot's own outbound echo and
|
|
11
|
+
* never becomes an inbound event; a message with no sender or no id cannot be deduplicated, so it
|
|
12
|
+
* is dropped rather than given a synthetic id that would collide on the next poll.
|
|
13
|
+
*
|
|
14
|
+
* The conversation coordinate is `group:<group_id>` when the message names a group, else
|
|
15
|
+
* `user:<from_user_id>`. The inbound `context_token` rides in `ref_handle` as `ctx:<token>`: the
|
|
16
|
+
* bridge hands it back as `reply_to` on the answer to this event, and iLink wants it verbatim on
|
|
17
|
+
* the send. `message_handle` stays the platform message id.
|
|
18
|
+
*/
|
|
19
|
+
export function normalizeMessage(message, now) {
|
|
20
|
+
if (message.message_type === 2)
|
|
21
|
+
return undefined;
|
|
22
|
+
const from = message.from_user_id;
|
|
23
|
+
if (typeof from !== 'string' || from.length === 0)
|
|
24
|
+
return undefined;
|
|
25
|
+
const id = message.message_id ?? message.seq;
|
|
26
|
+
if (id === undefined || id === null || String(id).length === 0)
|
|
27
|
+
return undefined;
|
|
28
|
+
const items = message.item_list ?? [];
|
|
29
|
+
const texts = [];
|
|
30
|
+
let transcript;
|
|
31
|
+
const attachments = [];
|
|
32
|
+
let parent;
|
|
33
|
+
for (const item of items) {
|
|
34
|
+
if (item.type === 1 && typeof item.text_item?.text === 'string')
|
|
35
|
+
texts.push(item.text_item.text);
|
|
36
|
+
if (transcript === undefined && item.type === 3) {
|
|
37
|
+
const voice = item.voice_item?.text;
|
|
38
|
+
if (typeof voice === 'string' && voice.length > 0)
|
|
39
|
+
transcript = voice;
|
|
40
|
+
}
|
|
41
|
+
const attachment = attachmentOf(item);
|
|
42
|
+
if (attachment !== undefined)
|
|
43
|
+
attachments.push(attachment);
|
|
44
|
+
if (parent === undefined) {
|
|
45
|
+
const quoted = item.ref_msg?.msg_id;
|
|
46
|
+
if (typeof quoted === 'string' && quoted.length > 0)
|
|
47
|
+
parent = quoted;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const text = texts.length > 0 ? texts.join('\n') : transcript ?? '';
|
|
51
|
+
const reply = parent === undefined ? undefined : { parent };
|
|
52
|
+
const groupId = message.group_id;
|
|
53
|
+
const conversation = typeof groupId === 'string' && groupId.length > 0 ? `group:${groupId}` : `user:${from}`;
|
|
54
|
+
const contextToken = message.context_token;
|
|
55
|
+
const createdAt = message.create_time_ms;
|
|
56
|
+
return {
|
|
57
|
+
platform: PLATFORM,
|
|
58
|
+
source_id: String(id),
|
|
59
|
+
conversation,
|
|
60
|
+
kind: 'message',
|
|
61
|
+
from,
|
|
62
|
+
text,
|
|
63
|
+
attachments,
|
|
64
|
+
...(reply === undefined ? {} : { reply }),
|
|
65
|
+
message_handle: String(id),
|
|
66
|
+
...(typeof contextToken === 'string' && contextToken.length > 0 ? { ref_handle: `ctx:${contextToken}` } : {}),
|
|
67
|
+
at: typeof createdAt === 'number' && Number.isFinite(createdAt) ? new Date(createdAt).toISOString() : new Date(now()).toISOString(),
|
|
68
|
+
raw: message,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** The media handle a later download needs: the pre-signed URL first, the encrypted reference second. */
|
|
72
|
+
function attachmentOf(item) {
|
|
73
|
+
const kind = item.type === undefined ? undefined : ATTACHMENT_KINDS[item.type];
|
|
74
|
+
if (kind === undefined)
|
|
75
|
+
return undefined;
|
|
76
|
+
const media = item.type === 2 ? item.image_item
|
|
77
|
+
: item.type === 3 ? item.voice_item
|
|
78
|
+
: item.type === 4 ? item.file_item
|
|
79
|
+
: item.video_item;
|
|
80
|
+
const handle = firstNonEmpty(media?.full_url) ?? firstNonEmpty(media?.encrypt_query_param);
|
|
81
|
+
if (handle === undefined)
|
|
82
|
+
return undefined;
|
|
83
|
+
const name = item.type === 4 ? firstNonEmpty(item.file_item?.file_name) : undefined;
|
|
84
|
+
return { kind, platform_handle: handle, ...(name === undefined ? {} : { alt: name }) };
|
|
85
|
+
}
|
|
86
|
+
function firstNonEmpty(value) {
|
|
87
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=normalize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize.js","sourceRoot":"","sources":["../src/normalize.ts"],"names":[],"mappings":"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"}
|
package/dist/port.d.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The iLink Bot API boundary: every platform call the connector makes goes through this interface,
|
|
3
|
+
* so the connector's translation logic runs against a fake in tests and the real HTTP API in
|
|
4
|
+
* production. Primary source: `developers.weixin.qq.com/doc/aispeech/knowledge/openapi/Clawbotrelated.*`
|
|
5
|
+
* and `Tencent/openclaw-weixin` `docs/protocol.md`.
|
|
6
|
+
*/
|
|
7
|
+
export declare const WECHAT_DEFAULT_BASE_URL = "https://ilinkai.weixin.qq.com";
|
|
8
|
+
/** One media reference inside a message item; `full_url` is pre-signed, the query param is not. */
|
|
9
|
+
export interface WechatMediaRef {
|
|
10
|
+
full_url?: string;
|
|
11
|
+
encrypt_query_param?: string;
|
|
12
|
+
aes_key?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface WechatMessageItem {
|
|
15
|
+
/** 1 text, 2 image, 3 voice, 4 file, 5 video. */
|
|
16
|
+
type?: number;
|
|
17
|
+
msg_id?: string;
|
|
18
|
+
ref_msg?: {
|
|
19
|
+
msg_id?: string;
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
};
|
|
22
|
+
text_item?: {
|
|
23
|
+
text?: string;
|
|
24
|
+
};
|
|
25
|
+
image_item?: WechatMediaRef;
|
|
26
|
+
voice_item?: WechatMediaRef & {
|
|
27
|
+
text?: string;
|
|
28
|
+
};
|
|
29
|
+
file_item?: WechatMediaRef & {
|
|
30
|
+
file_name?: string;
|
|
31
|
+
len?: number;
|
|
32
|
+
};
|
|
33
|
+
video_item?: WechatMediaRef;
|
|
34
|
+
}
|
|
35
|
+
export interface WechatMessage {
|
|
36
|
+
seq?: number;
|
|
37
|
+
message_id?: number | string;
|
|
38
|
+
from_user_id?: string;
|
|
39
|
+
to_user_id?: string;
|
|
40
|
+
client_id?: string;
|
|
41
|
+
create_time_ms?: number;
|
|
42
|
+
session_id?: string;
|
|
43
|
+
group_id?: string;
|
|
44
|
+
/** 1 user message, 2 the bot's own outbound. */
|
|
45
|
+
message_type?: number;
|
|
46
|
+
message_state?: number;
|
|
47
|
+
item_list?: WechatMessageItem[];
|
|
48
|
+
context_token?: string;
|
|
49
|
+
run_id?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface WechatUpdatesResponse {
|
|
52
|
+
ret: number;
|
|
53
|
+
errcode?: number;
|
|
54
|
+
errmsg?: string;
|
|
55
|
+
msgs?: WechatMessage[];
|
|
56
|
+
get_updates_buf?: string;
|
|
57
|
+
longpolling_timeout_ms?: number;
|
|
58
|
+
}
|
|
59
|
+
export interface WechatSendResponse {
|
|
60
|
+
ret: number;
|
|
61
|
+
errcode?: number;
|
|
62
|
+
errmsg?: string;
|
|
63
|
+
}
|
|
64
|
+
export interface WechatConfigResponse {
|
|
65
|
+
ret: number;
|
|
66
|
+
errcode?: number;
|
|
67
|
+
errmsg?: string;
|
|
68
|
+
typing_ticket?: string;
|
|
69
|
+
}
|
|
70
|
+
/** The `msg` envelope of `sendmessage`; the port adds `base_info` around it. */
|
|
71
|
+
export interface WechatOutboundMessage {
|
|
72
|
+
from_user_id: string;
|
|
73
|
+
to_user_id: string;
|
|
74
|
+
client_id: string;
|
|
75
|
+
message_type: number;
|
|
76
|
+
message_state: number;
|
|
77
|
+
context_token?: string;
|
|
78
|
+
item_list: Array<{
|
|
79
|
+
type: number;
|
|
80
|
+
text_item: {
|
|
81
|
+
text: string;
|
|
82
|
+
};
|
|
83
|
+
}>;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* One iLink answer that did not succeed. `ret`/`errmsg` are the platform's own rejection (it
|
|
87
|
+
* answered, so the operation is settled); without them the call never reached the platform and
|
|
88
|
+
* the caller cannot tell whether it landed.
|
|
89
|
+
*/
|
|
90
|
+
export declare class WechatPortError extends Error {
|
|
91
|
+
readonly status: number | undefined;
|
|
92
|
+
readonly ret: number | undefined;
|
|
93
|
+
readonly errcode: number | undefined;
|
|
94
|
+
readonly errmsg: string | undefined;
|
|
95
|
+
readonly retryable: boolean;
|
|
96
|
+
/** The caller's signal ended the call: a normal end of polling, not a fault. */
|
|
97
|
+
readonly aborted: boolean;
|
|
98
|
+
/** `ret` or `errcode` is -14: the bot token's session is gone until the QR is scanned again. */
|
|
99
|
+
readonly sessionExpired: boolean;
|
|
100
|
+
constructor(message: string, options?: {
|
|
101
|
+
status?: number;
|
|
102
|
+
ret?: number;
|
|
103
|
+
errcode?: number;
|
|
104
|
+
errmsg?: string;
|
|
105
|
+
retryable?: boolean;
|
|
106
|
+
aborted?: boolean;
|
|
107
|
+
cause?: unknown;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The platform calls this connector makes. `getUpdates` takes the caller's abort signal because
|
|
112
|
+
* the long poll is the one call a shutdown interrupts; `rateLimits` is the host's observation of
|
|
113
|
+
* HTTP 429 answers, counted here because this is where they arrive.
|
|
114
|
+
*/
|
|
115
|
+
export interface WechatPort {
|
|
116
|
+
getUpdates(cursor: string, signal?: AbortSignal): Promise<WechatUpdatesResponse>;
|
|
117
|
+
sendMessage(message: WechatOutboundMessage): Promise<WechatSendResponse>;
|
|
118
|
+
getConfig(userId: string, contextToken?: string): Promise<WechatConfigResponse>;
|
|
119
|
+
sendTyping(userId: string, typingTicket: string, status: 1 | 2): Promise<void>;
|
|
120
|
+
/** Lifecycle: a failure is logged by the caller, never fatal. */
|
|
121
|
+
notifyStart(): Promise<void>;
|
|
122
|
+
notifyStop(): Promise<void>;
|
|
123
|
+
rateLimits(): number;
|
|
124
|
+
}
|
|
125
|
+
export interface WechatPortOptions {
|
|
126
|
+
/** The bot token from the ClawBot QR login. */
|
|
127
|
+
token: string;
|
|
128
|
+
baseUrl?: string;
|
|
129
|
+
fetch?: (input: string, init: RequestInit) => Promise<Response>;
|
|
130
|
+
channelVersion?: string;
|
|
131
|
+
botAgent?: string;
|
|
132
|
+
/** `X-WECHAT-UIN` is a random uint32 per call; injected so a test can pin it. */
|
|
133
|
+
random?: () => number;
|
|
134
|
+
/** 0 disables the stall guard. */
|
|
135
|
+
timeoutMs?: number;
|
|
136
|
+
}
|
|
137
|
+
export declare function detailOf(error: unknown): string;
|
|
138
|
+
export declare function createWechatPort(options: WechatPortOptions): WechatPort;
|
|
139
|
+
//# sourceMappingURL=port.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"port.d.ts","sourceRoot":"","sources":["../src/port.ts"],"names":[],"mappings":"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"}
|
package/dist/port.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The iLink Bot API boundary: every platform call the connector makes goes through this interface,
|
|
3
|
+
* so the connector's translation logic runs against a fake in tests and the real HTTP API in
|
|
4
|
+
* production. Primary source: `developers.weixin.qq.com/doc/aispeech/knowledge/openapi/Clawbotrelated.*`
|
|
5
|
+
* and `Tencent/openclaw-weixin` `docs/protocol.md`.
|
|
6
|
+
*/
|
|
7
|
+
export const WECHAT_DEFAULT_BASE_URL = 'https://ilinkai.weixin.qq.com';
|
|
8
|
+
/** `iLink-App-ClientVersion`: 0x00010000, spelled as the decimal string the API takes. */
|
|
9
|
+
const CLIENT_VERSION = 65536;
|
|
10
|
+
const UINT32 = 0x1_0000_0000;
|
|
11
|
+
const DEFAULT_CHANNEL_VERSION = '3.0.0';
|
|
12
|
+
const DEFAULT_BOT_AGENT = 'Onlyne';
|
|
13
|
+
/** A long poll holds the request for about 35 s; this guards a call that never answers at all. */
|
|
14
|
+
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
15
|
+
/**
|
|
16
|
+
* One iLink answer that did not succeed. `ret`/`errmsg` are the platform's own rejection (it
|
|
17
|
+
* answered, so the operation is settled); without them the call never reached the platform and
|
|
18
|
+
* the caller cannot tell whether it landed.
|
|
19
|
+
*/
|
|
20
|
+
export class WechatPortError extends Error {
|
|
21
|
+
status;
|
|
22
|
+
ret;
|
|
23
|
+
errcode;
|
|
24
|
+
errmsg;
|
|
25
|
+
retryable;
|
|
26
|
+
/** The caller's signal ended the call: a normal end of polling, not a fault. */
|
|
27
|
+
aborted;
|
|
28
|
+
/** `ret` or `errcode` is -14: the bot token's session is gone until the QR is scanned again. */
|
|
29
|
+
sessionExpired;
|
|
30
|
+
constructor(message, options = {}) {
|
|
31
|
+
super(message, options.cause === undefined ? undefined : { cause: options.cause });
|
|
32
|
+
this.name = 'WechatPortError';
|
|
33
|
+
this.status = options.status;
|
|
34
|
+
this.ret = options.ret;
|
|
35
|
+
this.errcode = options.errcode;
|
|
36
|
+
this.errmsg = options.errmsg;
|
|
37
|
+
this.retryable = options.retryable ?? false;
|
|
38
|
+
this.aborted = options.aborted ?? false;
|
|
39
|
+
this.sessionExpired = options.ret === -14 || options.errcode === -14;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function detailOf(error) {
|
|
43
|
+
return error instanceof Error ? error.message : String(error);
|
|
44
|
+
}
|
|
45
|
+
export function createWechatPort(options) {
|
|
46
|
+
const baseUrl = (options.baseUrl ?? WECHAT_DEFAULT_BASE_URL).replace(/\/+$/, '');
|
|
47
|
+
const fetchImpl = options.fetch ?? ((input, init) => fetch(input, init));
|
|
48
|
+
const random = options.random ?? Math.random;
|
|
49
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
50
|
+
let rateLimits = 0;
|
|
51
|
+
const uin = () => {
|
|
52
|
+
const draw = Math.floor(Math.abs(random()) * UINT32) % UINT32;
|
|
53
|
+
return Buffer.from(String(draw), 'utf8').toString('base64');
|
|
54
|
+
};
|
|
55
|
+
const headers = () => ({
|
|
56
|
+
'Content-Type': 'application/json',
|
|
57
|
+
AuthorizationType: 'ilink_bot_token',
|
|
58
|
+
Authorization: `Bearer ${options.token}`,
|
|
59
|
+
'X-WECHAT-UIN': uin(),
|
|
60
|
+
'iLink-App-Id': 'bot',
|
|
61
|
+
'iLink-App-ClientVersion': String(CLIENT_VERSION),
|
|
62
|
+
});
|
|
63
|
+
const baseInfo = () => ({
|
|
64
|
+
channel_version: options.channelVersion ?? DEFAULT_CHANNEL_VERSION,
|
|
65
|
+
bot_agent: options.botAgent ?? DEFAULT_BOT_AGENT,
|
|
66
|
+
});
|
|
67
|
+
/**
|
|
68
|
+
* One authenticated POST. `call` names the call in the error text, because the platform's
|
|
69
|
+
* `errmsg` answers "what did the platform say" and never says which call asked.
|
|
70
|
+
*/
|
|
71
|
+
async function post(path, call, body, signal) {
|
|
72
|
+
const guard = timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : undefined;
|
|
73
|
+
const combined = signal !== undefined && guard !== undefined
|
|
74
|
+
? AbortSignal.any([signal, guard])
|
|
75
|
+
: (signal ?? guard);
|
|
76
|
+
let response;
|
|
77
|
+
try {
|
|
78
|
+
response = await fetchImpl(`${baseUrl}${path}`, {
|
|
79
|
+
method: 'POST',
|
|
80
|
+
headers: headers(),
|
|
81
|
+
body: JSON.stringify(body),
|
|
82
|
+
...(combined === undefined ? {} : { signal: combined }),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch (failure) {
|
|
86
|
+
if (signal?.aborted === true) {
|
|
87
|
+
throw new WechatPortError('The platform call was aborted.', { aborted: true, retryable: true, cause: failure });
|
|
88
|
+
}
|
|
89
|
+
if (guard?.aborted === true) {
|
|
90
|
+
throw new WechatPortError(`The platform call failed: ${call} made no answer within ${timeoutMs} ms.`, { retryable: true, cause: failure });
|
|
91
|
+
}
|
|
92
|
+
throw new WechatPortError(`The platform call failed: ${detailOf(failure)}`, { retryable: true, cause: failure });
|
|
93
|
+
}
|
|
94
|
+
const text = await response.text();
|
|
95
|
+
if (response.status === 429) {
|
|
96
|
+
rateLimits += 1;
|
|
97
|
+
throw new WechatPortError(`The platform rate-limited ${call}: 429 ${text.trim()}`, { status: 429, retryable: true });
|
|
98
|
+
}
|
|
99
|
+
if (!response.ok) {
|
|
100
|
+
throw new WechatPortError(`The platform call failed: ${response.status} ${text.trim()}`, {
|
|
101
|
+
status: response.status,
|
|
102
|
+
retryable: response.status >= 500,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
let parsed;
|
|
106
|
+
try {
|
|
107
|
+
parsed = JSON.parse(text);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
throw new WechatPortError(`The platform call failed: ${response.status} returned a body that is not JSON.`, { status: response.status });
|
|
111
|
+
}
|
|
112
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
113
|
+
throw new WechatPortError(`The platform call failed: ${response.status} returned no JSON object.`, { status: response.status });
|
|
114
|
+
}
|
|
115
|
+
const record = parsed;
|
|
116
|
+
const ret = typeof record.ret === 'number' ? record.ret : 0;
|
|
117
|
+
const errcode = typeof record.errcode === 'number' ? record.errcode : undefined;
|
|
118
|
+
const errmsg = typeof record.errmsg === 'string' && record.errmsg.length > 0 ? record.errmsg : undefined;
|
|
119
|
+
if (ret !== 0 || (errcode !== undefined && errcode !== 0)) {
|
|
120
|
+
const reason = errmsg ?? `ret ${ret}${errcode === undefined || errcode === ret ? '' : ` errcode ${errcode}`}`;
|
|
121
|
+
throw new WechatPortError(`The platform rejected ${call}: ${reason}`, {
|
|
122
|
+
status: response.status,
|
|
123
|
+
ret,
|
|
124
|
+
...(errcode === undefined ? {} : { errcode }),
|
|
125
|
+
...(errmsg === undefined ? {} : { errmsg }),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return parsed;
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
async getUpdates(cursor, signal) {
|
|
132
|
+
try {
|
|
133
|
+
return await post('/ilink/bot/getupdates', 'the poll call', { get_updates_buf: cursor, base_info: baseInfo() }, signal);
|
|
134
|
+
}
|
|
135
|
+
catch (failure) {
|
|
136
|
+
if (failure instanceof WechatPortError && failure.aborted)
|
|
137
|
+
return { ret: 0, msgs: [] };
|
|
138
|
+
throw failure;
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
async sendMessage(message) {
|
|
142
|
+
return post('/ilink/bot/sendmessage', 'the send', { msg: message, base_info: baseInfo() });
|
|
143
|
+
},
|
|
144
|
+
async getConfig(userId, contextToken) {
|
|
145
|
+
return post('/ilink/bot/getconfig', 'the getconfig call', {
|
|
146
|
+
ilink_user_id: userId,
|
|
147
|
+
...(contextToken === undefined ? {} : { context_token: contextToken }),
|
|
148
|
+
base_info: baseInfo(),
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
async sendTyping(userId, typingTicket, status) {
|
|
152
|
+
await post('/ilink/bot/sendtyping', 'the sendtyping call', {
|
|
153
|
+
ilink_user_id: userId,
|
|
154
|
+
typing_ticket: typingTicket,
|
|
155
|
+
status,
|
|
156
|
+
base_info: baseInfo(),
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
async notifyStart() {
|
|
160
|
+
await post('/ilink/bot/msg/notifystart', 'the notifystart call', { base_info: baseInfo() });
|
|
161
|
+
},
|
|
162
|
+
async notifyStop() {
|
|
163
|
+
await post('/ilink/bot/msg/notifystop', 'the notifystop call', { base_info: baseInfo() });
|
|
164
|
+
},
|
|
165
|
+
rateLimits: () => rateLimits,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=port.js.map
|
package/dist/port.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"port.js","sourceRoot":"","sources":["../src/port.ts"],"names":[],"mappings":"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"}
|
package/dist/send.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { SendOutcome, SendRequest } from '@onlyne/im-core';
|
|
2
|
+
import { type WechatOutboundMessage } from './port.js';
|
|
3
|
+
/** The one platform call a send makes; the connector's port satisfies it. */
|
|
4
|
+
export interface SendPort {
|
|
5
|
+
sendMessage(message: WechatOutboundMessage): Promise<unknown>;
|
|
6
|
+
}
|
|
7
|
+
/** The conversation coordinate iLink addresses: `user:<id>`, or `group:<id>` whose id goes to `to_user_id`. */
|
|
8
|
+
export declare function conversationTarget(conversation: string): {
|
|
9
|
+
scheme: 'user' | 'group';
|
|
10
|
+
id: string;
|
|
11
|
+
} | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* One text send. `reply_to` starting with `ctx:` is the inbound event's transport context, used
|
|
14
|
+
* as `context_token`. iLink exposes no message-quoting field, so any other handle is not echoed;
|
|
15
|
+
* the text still goes out and the platform decides.
|
|
16
|
+
*/
|
|
17
|
+
export declare function sendMessage(port: SendPort, request: SendRequest): Promise<SendOutcome>;
|
|
18
|
+
/**
|
|
19
|
+
* A platform answer (`ret`/`errmsg` present) settles the operation: the platform saw the call and
|
|
20
|
+
* refused it. Anything else — no answer, an HTTP failure, a rate limit — leaves it unknown,
|
|
21
|
+
* because the send may still have landed.
|
|
22
|
+
*/
|
|
23
|
+
export declare function outcomeOf(failure: unknown): SendOutcome;
|
|
24
|
+
//# sourceMappingURL=send.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send.d.ts","sourceRoot":"","sources":["../src/send.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAA6B,KAAK,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAElF,6EAA6E;AAC7E,MAAM,WAAW,QAAQ;IACvB,WAAW,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC/D;AAID,+GAA+G;AAC/G,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAU7G;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAgC5F;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,CAKvD"}
|
package/dist/send.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { WechatPortError, detailOf } from './port.js';
|
|
2
|
+
const UNSUPPORTED = 'unsupported by @onlyne/im-wechat';
|
|
3
|
+
/** The conversation coordinate iLink addresses: `user:<id>`, or `group:<id>` whose id goes to `to_user_id`. */
|
|
4
|
+
export function conversationTarget(conversation) {
|
|
5
|
+
if (conversation.startsWith('user:')) {
|
|
6
|
+
const id = conversation.slice('user:'.length);
|
|
7
|
+
return id.length === 0 ? undefined : { scheme: 'user', id };
|
|
8
|
+
}
|
|
9
|
+
if (conversation.startsWith('group:')) {
|
|
10
|
+
const id = conversation.slice('group:'.length);
|
|
11
|
+
return id.length === 0 ? undefined : { scheme: 'group', id };
|
|
12
|
+
}
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* One text send. `reply_to` starting with `ctx:` is the inbound event's transport context, used
|
|
17
|
+
* as `context_token`. iLink exposes no message-quoting field, so any other handle is not echoed;
|
|
18
|
+
* the text still goes out and the platform decides.
|
|
19
|
+
*/
|
|
20
|
+
export async function sendMessage(port, request) {
|
|
21
|
+
if (request.attachments.length > 0 || request.card !== undefined) {
|
|
22
|
+
return {
|
|
23
|
+
state: 'committed',
|
|
24
|
+
error: `${UNSUPPORTED}: the iLink Bot API path implemented here sends text only; attachments and cards are not sent.`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const target = conversationTarget(request.conversation);
|
|
28
|
+
if (target === undefined) {
|
|
29
|
+
return { state: 'committed', error: `${UNSUPPORTED}: conversation ${request.conversation} is not user:<id> or group:<id>.` };
|
|
30
|
+
}
|
|
31
|
+
const replyTo = request.reply_to;
|
|
32
|
+
// `ctx:` is the inbound event's transport context. Any other handle is the event's message id,
|
|
33
|
+
// which the iLink Bot API has no field to quote; the text is still sent and the platform's own
|
|
34
|
+
// answer decides, instead of this connector refusing a reply the bridge legitimately addressed.
|
|
35
|
+
const contextToken = replyTo !== undefined && replyTo.startsWith('ctx:') ? replyTo.slice('ctx:'.length) : undefined;
|
|
36
|
+
const clientId = request.request_id ?? crypto.randomUUID();
|
|
37
|
+
try {
|
|
38
|
+
await port.sendMessage({
|
|
39
|
+
from_user_id: '',
|
|
40
|
+
to_user_id: target.id,
|
|
41
|
+
client_id: clientId,
|
|
42
|
+
message_type: 2,
|
|
43
|
+
message_state: 2,
|
|
44
|
+
...(contextToken === undefined || contextToken.length === 0 ? {} : { context_token: contextToken }),
|
|
45
|
+
item_list: [{ type: 1, text_item: { text: request.text } }],
|
|
46
|
+
});
|
|
47
|
+
return { state: 'committed', message_id: clientId };
|
|
48
|
+
}
|
|
49
|
+
catch (failure) {
|
|
50
|
+
return outcomeOf(failure);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A platform answer (`ret`/`errmsg` present) settles the operation: the platform saw the call and
|
|
55
|
+
* refused it. Anything else — no answer, an HTTP failure, a rate limit — leaves it unknown,
|
|
56
|
+
* because the send may still have landed.
|
|
57
|
+
*/
|
|
58
|
+
export function outcomeOf(failure) {
|
|
59
|
+
if (failure instanceof WechatPortError && failure.ret !== undefined) {
|
|
60
|
+
return { state: 'committed', error: failure.message };
|
|
61
|
+
}
|
|
62
|
+
return { state: 'unknown', error: detailOf(failure) };
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=send.js.map
|
package/dist/send.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"send.js","sourceRoot":"","sources":["../src/send.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onlyne/im-wechat",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "3.2.0",
|
|
4
|
+
"description": "WeChat connector for the Onlyne IM gateway over the Tencent iLink / ClawBot Bot API: inbound polling and text sends",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/dbydd/onlyne.git",
|
|
12
|
+
"directory": "packages/im-wechat"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/dbydd/onlyne#readme",
|
|
15
|
+
"bugs": { "url": "https://github.com/dbydd/onlyne/issues" },
|
|
16
|
+
"keywords": ["onlyne", "wechat", "weixin", "ilink", "clawbot", "im", "connector"],
|
|
17
|
+
"publishConfig": { "access": "public" },
|
|
18
|
+
"engines": { "bun": ">=1.4.3" },
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"files": ["dist", "LICENSE"],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc",
|
|
28
|
+
"check": "tsc && tsc -p tsconfig.tests.json",
|
|
29
|
+
"test": "bun tests/verify.ts && bun tests/send.ts && bun tests/poll.ts",
|
|
30
|
+
"prepack": "bun run build"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@onlyne/im-core": "^3.2.0",
|
|
34
|
+
"@onlyne/lib": "^3.2.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/bun": "^1.3.0",
|
|
38
|
+
"typescript": "^5.9.0"
|
|
39
|
+
}
|
|
40
|
+
}
|