@tencent-weixin/openclaw-weixin 2.1.8 → 2.1.10
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 +16 -0
- package/CHANGELOG.zh_CN.md +16 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/api/api.ts +34 -0
- package/src/api/types.ts +22 -0
- package/src/channel.ts +98 -24
- package/src/messaging/outbound-hooks.ts +84 -0
- package/src/messaging/process-message.ts +18 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
This project follows the [Keep a Changelog](https://keepachangelog.com/) format.
|
|
6
6
|
|
|
7
|
+
## [2.1.9] - 2026-04-20
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Outbound hook support:** Add `message_sending` (pre-send interception/modification) and `message_sent` (post-send notification) hook integration for all outbound paths — `sendText`, `sendMedia`, and the inbound-reply `deliver` in `process-message`. Hook logic is extracted into a shared `src/messaging/outbound-hooks.ts` module.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- **Cleanup:** Remove unused `mediaUrl` parameter from `sendWeixinOutbound` signature.
|
|
16
|
+
|
|
17
|
+
## [2.1.8] - 2026-04-07
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **Markdown filter:** `StreamingMarkdownFilter` now preserves more Markdown constructs in outbound text.
|
|
22
|
+
|
|
7
23
|
## [2.1.7] - 2026-04-07
|
|
8
24
|
|
|
9
25
|
### Fixed
|
package/CHANGELOG.zh_CN.md
CHANGED
|
@@ -4,6 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
本项目遵循 [Keep a Changelog](https://keepachangelog.com/) 格式。
|
|
6
6
|
|
|
7
|
+
## [2.1.9] - 2026-04-20
|
|
8
|
+
|
|
9
|
+
### 新增
|
|
10
|
+
|
|
11
|
+
- **外发 hook 支持:** 为所有外发路径(`sendText`、`sendMedia`、`process-message` 中的入站回复 `deliver`)接入 `message_sending`(发送前拦截/修改)和 `message_sent`(发送后通知)hook。hook 逻辑抽取至共享模块 `src/messaging/outbound-hooks.ts`。
|
|
12
|
+
|
|
13
|
+
### 变更
|
|
14
|
+
|
|
15
|
+
- **清理:** 移除 `sendWeixinOutbound` 签名中未使用的 `mediaUrl` 参数。
|
|
16
|
+
|
|
17
|
+
## [2.1.8] - 2026-04-07
|
|
18
|
+
|
|
19
|
+
### 变更
|
|
20
|
+
|
|
21
|
+
- **Markdown 过滤器:** `StreamingMarkdownFilter` 放开了更多 Markdown 格式的保留。
|
|
22
|
+
|
|
7
23
|
## [2.1.7] - 2026-04-07
|
|
8
24
|
|
|
9
25
|
### 修复
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/src/api/api.ts
CHANGED
|
@@ -13,6 +13,8 @@ import type {
|
|
|
13
13
|
GetUploadUrlResp,
|
|
14
14
|
GetUpdatesReq,
|
|
15
15
|
GetUpdatesResp,
|
|
16
|
+
NotifyStopResp,
|
|
17
|
+
NotifyStartResp,
|
|
16
18
|
SendMessageReq,
|
|
17
19
|
SendTypingReq,
|
|
18
20
|
GetConfigResp,
|
|
@@ -316,3 +318,35 @@ export async function sendTyping(
|
|
|
316
318
|
label: "sendTyping",
|
|
317
319
|
});
|
|
318
320
|
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Notify Weixin that this channel client is stopping (gateway shutdown / channel stop).
|
|
324
|
+
* Uses a standalone timeout (not the gateway abort signal) so the request can finish
|
|
325
|
+
* after OpenClaw has already aborted the long-poll.
|
|
326
|
+
*/
|
|
327
|
+
export async function notifyStop(params: WeixinApiOptions): Promise<NotifyStopResp> {
|
|
328
|
+
const rawText = await apiPostFetch({
|
|
329
|
+
baseUrl: params.baseUrl,
|
|
330
|
+
endpoint: "ilink/bot/msg/notifystop",
|
|
331
|
+
body: JSON.stringify({ base_info: buildBaseInfo() }),
|
|
332
|
+
token: params.token,
|
|
333
|
+
timeoutMs: params.timeoutMs ?? DEFAULT_CONFIG_TIMEOUT_MS,
|
|
334
|
+
label: "notifyStop",
|
|
335
|
+
});
|
|
336
|
+
return JSON.parse(rawText) as NotifyStopResp;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Notify Weixin that this channel client is starting (gateway startup / channel start).
|
|
341
|
+
*/
|
|
342
|
+
export async function notifyStart(params: WeixinApiOptions): Promise<NotifyStartResp> {
|
|
343
|
+
const rawText = await apiPostFetch({
|
|
344
|
+
baseUrl: params.baseUrl,
|
|
345
|
+
endpoint: "ilink/bot/msg/notifystart",
|
|
346
|
+
body: JSON.stringify({ base_info: buildBaseInfo() }),
|
|
347
|
+
token: params.token,
|
|
348
|
+
timeoutMs: params.timeoutMs ?? DEFAULT_CONFIG_TIMEOUT_MS,
|
|
349
|
+
label: "notifyStart",
|
|
350
|
+
});
|
|
351
|
+
return JSON.parse(rawText) as NotifyStartResp;
|
|
352
|
+
}
|
package/src/api/types.ts
CHANGED
|
@@ -224,3 +224,25 @@ export interface GetConfigResp {
|
|
|
224
224
|
/** Base64-encoded typing ticket for sendTyping. */
|
|
225
225
|
typing_ticket?: string;
|
|
226
226
|
}
|
|
227
|
+
|
|
228
|
+
/** proto: NotifyStopReq — notify server when the channel client is stopping. */
|
|
229
|
+
export interface NotifyStopReq {
|
|
230
|
+
base_info?: BaseInfo;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** proto: NotifyStopResp */
|
|
234
|
+
export interface NotifyStopResp {
|
|
235
|
+
ret?: number;
|
|
236
|
+
errmsg?: string;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** proto: NotifyStartReq — notify server when the channel client is starting. */
|
|
240
|
+
export interface NotifyStartReq {
|
|
241
|
+
base_info?: BaseInfo;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** proto: NotifyStartResp */
|
|
245
|
+
export interface NotifyStartResp {
|
|
246
|
+
ret?: number;
|
|
247
|
+
errmsg?: string;
|
|
248
|
+
}
|
package/src/channel.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
DEFAULT_BASE_URL,
|
|
16
16
|
} from "./auth/accounts.js";
|
|
17
17
|
import type { ResolvedWeixinAccount } from "./auth/accounts.js";
|
|
18
|
+
import { notifyStop, notifyStart } from "./api/api.js";
|
|
18
19
|
import { assertSessionActive } from "./api/session-guard.js";
|
|
19
20
|
import { getContextToken, findAccountIdsByContextToken, restoreContextTokens, clearContextTokensForAccount } from "./messaging/inbound.js";
|
|
20
21
|
import { logger } from "./util/logger.js";
|
|
@@ -27,6 +28,7 @@ import type { WeixinQrStartResult, WeixinQrWaitResult } from "./auth/login-qr.js
|
|
|
27
28
|
// Lazy-imported inside startAccount to avoid pulling in the monitor -> process-message ->
|
|
28
29
|
// command-auth chain during plugin registration, which can re-enter plugin/provider registry
|
|
29
30
|
// resolution before the account actually starts.
|
|
31
|
+
import { applyWeixinMessageSendingHook, emitWeixinMessageSent } from "./messaging/outbound-hooks.js";
|
|
30
32
|
import { sendWeixinMediaFile } from "./messaging/send-media.js";
|
|
31
33
|
import { sendMessageWeixin, StreamingMarkdownFilter } from "./messaging/send.js";
|
|
32
34
|
import { downloadRemoteImageToTemp } from "./cdn/upload.js";
|
|
@@ -109,7 +111,6 @@ async function sendWeixinOutbound(params: {
|
|
|
109
111
|
text: string;
|
|
110
112
|
accountId?: string | null;
|
|
111
113
|
contextToken?: string;
|
|
112
|
-
mediaUrl?: string;
|
|
113
114
|
}): Promise<{ channel: string; messageId: string }> {
|
|
114
115
|
const account = resolveWeixinAccount(params.cfg, params.accountId);
|
|
115
116
|
const aLog = logger.withAccount(account.accountId);
|
|
@@ -123,13 +124,31 @@ async function sendWeixinOutbound(params: {
|
|
|
123
124
|
}
|
|
124
125
|
const f = new StreamingMarkdownFilter();
|
|
125
126
|
const rawText = params.text ?? "";
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
127
|
+
let filteredText = f.feed(rawText) + f.flush();
|
|
128
|
+
|
|
129
|
+
const sendingResult = await applyWeixinMessageSendingHook({
|
|
130
|
+
to: params.to,
|
|
131
|
+
text: filteredText,
|
|
132
|
+
accountId: account.accountId,
|
|
133
|
+
});
|
|
134
|
+
if (sendingResult.cancelled) {
|
|
135
|
+
aLog.info(`sendWeixinOutbound: cancelled by message_sending hook to=${params.to}`);
|
|
136
|
+
return { channel: "openclaw-weixin", messageId: "" };
|
|
137
|
+
}
|
|
138
|
+
filteredText = sendingResult.text;
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const result = await sendMessageWeixin({ to: params.to, text: filteredText, opts: {
|
|
142
|
+
baseUrl: account.baseUrl,
|
|
143
|
+
token: account.token,
|
|
144
|
+
contextToken: params.contextToken,
|
|
145
|
+
}});
|
|
146
|
+
emitWeixinMessageSent({ to: params.to, content: filteredText, success: true, accountId: account.accountId });
|
|
147
|
+
return { channel: "openclaw-weixin", messageId: result.messageId };
|
|
148
|
+
} catch (err) {
|
|
149
|
+
emitWeixinMessageSent({ to: params.to, content: filteredText, success: false, error: String(err), accountId: account.accountId });
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
133
152
|
}
|
|
134
153
|
|
|
135
154
|
export const weixinPlugin: ChannelPlugin<ResolvedWeixinAccount> = {
|
|
@@ -215,6 +234,19 @@ export const weixinPlugin: ChannelPlugin<ResolvedWeixinAccount> = {
|
|
|
215
234
|
}
|
|
216
235
|
|
|
217
236
|
const mediaUrl = ctx.mediaUrl;
|
|
237
|
+
let text = ctx.text ?? "";
|
|
238
|
+
|
|
239
|
+
const sendingResult = await applyWeixinMessageSendingHook({
|
|
240
|
+
to: ctx.to,
|
|
241
|
+
text,
|
|
242
|
+
accountId: account.accountId,
|
|
243
|
+
mediaUrl,
|
|
244
|
+
});
|
|
245
|
+
if (sendingResult.cancelled) {
|
|
246
|
+
aLog.info(`sendMedia: cancelled by message_sending hook to=${ctx.to}`);
|
|
247
|
+
return { channel: "openclaw-weixin", messageId: "" };
|
|
248
|
+
}
|
|
249
|
+
text = sendingResult.text;
|
|
218
250
|
|
|
219
251
|
if (mediaUrl && (isLocalFilePath(mediaUrl) || isRemoteUrl(mediaUrl))) {
|
|
220
252
|
let filePath: string;
|
|
@@ -227,24 +259,35 @@ export const weixinPlugin: ChannelPlugin<ResolvedWeixinAccount> = {
|
|
|
227
259
|
aLog.debug(`sendMedia: remote image downloaded to ${filePath}`);
|
|
228
260
|
}
|
|
229
261
|
const contextToken = getContextToken(account.accountId, ctx.to);
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
262
|
+
try {
|
|
263
|
+
const result = await sendWeixinMediaFile({
|
|
264
|
+
filePath,
|
|
265
|
+
to: ctx.to,
|
|
266
|
+
text,
|
|
267
|
+
opts: { baseUrl: account.baseUrl, token: account.token, contextToken },
|
|
268
|
+
cdnBaseUrl: account.cdnBaseUrl,
|
|
269
|
+
});
|
|
270
|
+
emitWeixinMessageSent({ to: ctx.to, content: text, success: true, accountId: account.accountId });
|
|
271
|
+
return { channel: "openclaw-weixin", messageId: result.messageId };
|
|
272
|
+
} catch (err) {
|
|
273
|
+
emitWeixinMessageSent({ to: ctx.to, content: text, success: false, error: String(err), accountId: account.accountId });
|
|
274
|
+
throw err;
|
|
275
|
+
}
|
|
238
276
|
}
|
|
239
277
|
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
to: ctx.to,
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
278
|
+
const contextToken = getContextToken(account.accountId, ctx.to);
|
|
279
|
+
try {
|
|
280
|
+
const result = await sendMessageWeixin({ to: ctx.to, text, opts: {
|
|
281
|
+
baseUrl: account.baseUrl,
|
|
282
|
+
token: account.token,
|
|
283
|
+
contextToken,
|
|
284
|
+
}});
|
|
285
|
+
emitWeixinMessageSent({ to: ctx.to, content: text, success: true, accountId: account.accountId });
|
|
286
|
+
return { channel: "openclaw-weixin", messageId: result.messageId };
|
|
287
|
+
} catch (err) {
|
|
288
|
+
emitWeixinMessageSent({ to: ctx.to, content: text, success: false, error: String(err), accountId: account.accountId });
|
|
289
|
+
throw err;
|
|
290
|
+
}
|
|
248
291
|
},
|
|
249
292
|
},
|
|
250
293
|
status: {
|
|
@@ -385,6 +428,18 @@ export const weixinPlugin: ChannelPlugin<ResolvedWeixinAccount> = {
|
|
|
385
428
|
|
|
386
429
|
ctx.log?.info?.(`[${account.accountId}] starting weixin provider (${DEFAULT_BASE_URL})`);
|
|
387
430
|
|
|
431
|
+
try {
|
|
432
|
+
const resp = await notifyStart({
|
|
433
|
+
baseUrl: account.baseUrl,
|
|
434
|
+
token: account.token,
|
|
435
|
+
});
|
|
436
|
+
if (resp.ret !== undefined && resp.ret !== 0) {
|
|
437
|
+
aLog.warn(`notifyStart: ret=${resp.ret} errmsg=${resp.errmsg ?? ""}`);
|
|
438
|
+
}
|
|
439
|
+
} catch (err) {
|
|
440
|
+
aLog.warn(`notifyStart failed during startup (ignored): ${String(err)}`);
|
|
441
|
+
}
|
|
442
|
+
|
|
388
443
|
const logPath = aLog.getLogFilePath();
|
|
389
444
|
ctx.log?.info?.(`[${account.accountId}] weixin logs: ${logPath}`);
|
|
390
445
|
|
|
@@ -400,6 +455,25 @@ export const weixinPlugin: ChannelPlugin<ResolvedWeixinAccount> = {
|
|
|
400
455
|
setStatus: ctx.setStatus,
|
|
401
456
|
});
|
|
402
457
|
},
|
|
458
|
+
stopAccount: async (ctx) => {
|
|
459
|
+
const account = ctx.account;
|
|
460
|
+
const aLog = logger.withAccount(account.accountId);
|
|
461
|
+
if (!account.configured || !account.token?.trim()) {
|
|
462
|
+
aLog.debug(`gateway.stopAccount: skip notifyStop (not configured or no token)`);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
try {
|
|
466
|
+
const resp = await notifyStop({
|
|
467
|
+
baseUrl: account.baseUrl,
|
|
468
|
+
token: account.token,
|
|
469
|
+
});
|
|
470
|
+
if (resp.ret !== undefined && resp.ret !== 0) {
|
|
471
|
+
aLog.warn(`notifyStop: ret=${resp.ret} errmsg=${resp.errmsg ?? ""}`);
|
|
472
|
+
}
|
|
473
|
+
} catch (err) {
|
|
474
|
+
aLog.warn(`notifyStop failed during shutdown (ignored): ${String(err)}`);
|
|
475
|
+
}
|
|
476
|
+
},
|
|
403
477
|
loginWithQrStart: async ({ accountId, force, timeoutMs, verbose }) => {
|
|
404
478
|
// For re-login: use saved baseUrl from account data; fall back to default for new accounts.
|
|
405
479
|
const savedBaseUrl = accountId ? loadWeixinAccount(accountId)?.baseUrl?.trim() : "";
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fireAndForgetHook,
|
|
3
|
+
buildCanonicalSentMessageHookContext,
|
|
4
|
+
toPluginMessageContext,
|
|
5
|
+
toPluginMessageSentEvent,
|
|
6
|
+
} from "openclaw/plugin-sdk/hook-runtime";
|
|
7
|
+
import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
|
|
8
|
+
|
|
9
|
+
import { logger } from "../util/logger.js";
|
|
10
|
+
|
|
11
|
+
const CHANNEL_ID = "openclaw-weixin";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Run message_sending hook before sending.
|
|
15
|
+
* Returns the (possibly modified) text content plus a cancelled flag.
|
|
16
|
+
* Hook errors are caught and logged — sending proceeds regardless.
|
|
17
|
+
*/
|
|
18
|
+
export async function applyWeixinMessageSendingHook(params: {
|
|
19
|
+
to: string;
|
|
20
|
+
text: string;
|
|
21
|
+
accountId?: string;
|
|
22
|
+
mediaUrl?: string;
|
|
23
|
+
}): Promise<{ cancelled: boolean; text: string }> {
|
|
24
|
+
const hookRunner = getGlobalHookRunner();
|
|
25
|
+
if (!hookRunner?.hasHooks("message_sending")) {
|
|
26
|
+
return { cancelled: false, text: params.text };
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const hookResult = await hookRunner.runMessageSending(
|
|
30
|
+
{
|
|
31
|
+
to: params.to,
|
|
32
|
+
content: params.text,
|
|
33
|
+
metadata: {
|
|
34
|
+
channel: CHANNEL_ID,
|
|
35
|
+
accountId: params.accountId,
|
|
36
|
+
...(params.mediaUrl ? { mediaUrls: [params.mediaUrl] } : {}),
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{ channelId: CHANNEL_ID, accountId: params.accountId },
|
|
40
|
+
);
|
|
41
|
+
if (hookResult?.cancel) {
|
|
42
|
+
return { cancelled: true, text: params.text };
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
cancelled: false,
|
|
46
|
+
text: hookResult?.content ?? params.text,
|
|
47
|
+
};
|
|
48
|
+
} catch (err) {
|
|
49
|
+
logger.warn(`message_sending hook error, proceeding with send: ${String(err)}`);
|
|
50
|
+
return { cancelled: false, text: params.text };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Fire message_sent hook (fire-and-forget) after a send attempt.
|
|
56
|
+
*/
|
|
57
|
+
export function emitWeixinMessageSent(params: {
|
|
58
|
+
to: string;
|
|
59
|
+
content: string;
|
|
60
|
+
success: boolean;
|
|
61
|
+
error?: string;
|
|
62
|
+
accountId?: string;
|
|
63
|
+
}): void {
|
|
64
|
+
const hookRunner = getGlobalHookRunner();
|
|
65
|
+
if (!hookRunner?.hasHooks("message_sent")) return;
|
|
66
|
+
const canonical = buildCanonicalSentMessageHookContext({
|
|
67
|
+
to: params.to,
|
|
68
|
+
content: params.content,
|
|
69
|
+
success: params.success,
|
|
70
|
+
error: params.error,
|
|
71
|
+
channelId: CHANNEL_ID,
|
|
72
|
+
accountId: params.accountId,
|
|
73
|
+
conversationId: params.to,
|
|
74
|
+
});
|
|
75
|
+
fireAndForgetHook(
|
|
76
|
+
Promise.resolve(
|
|
77
|
+
hookRunner!.runMessageSent(
|
|
78
|
+
toPluginMessageSentEvent(canonical),
|
|
79
|
+
toPluginMessageContext(canonical),
|
|
80
|
+
),
|
|
81
|
+
),
|
|
82
|
+
"weixin: message_sent plugin hook failed",
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -20,6 +20,7 @@ import { redactBody, redactToken } from "../util/redact.js";
|
|
|
20
20
|
|
|
21
21
|
import { isDebugMode } from "./debug-mode.js";
|
|
22
22
|
import { sendWeixinErrorNotice } from "./error-notice.js";
|
|
23
|
+
import { applyWeixinMessageSendingHook, emitWeixinMessageSent } from "./outbound-hooks.js";
|
|
23
24
|
import {
|
|
24
25
|
setContextToken,
|
|
25
26
|
weixinMessageToMsgContext,
|
|
@@ -311,7 +312,7 @@ export async function processOneMessage(
|
|
|
311
312
|
typingCallbacks,
|
|
312
313
|
deliver: async (payload) => {
|
|
313
314
|
const rawText = payload.text ?? "";
|
|
314
|
-
|
|
315
|
+
let text = (() => {
|
|
315
316
|
const f = new StreamingMarkdownFilter();
|
|
316
317
|
return f.feed(rawText) + f.flush();
|
|
317
318
|
})();
|
|
@@ -330,11 +331,22 @@ export async function processOneMessage(
|
|
|
330
331
|
});
|
|
331
332
|
}
|
|
332
333
|
|
|
334
|
+
const sendingResult = await applyWeixinMessageSendingHook({
|
|
335
|
+
to: ctx.To,
|
|
336
|
+
text,
|
|
337
|
+
accountId: deps.accountId,
|
|
338
|
+
mediaUrl,
|
|
339
|
+
});
|
|
340
|
+
if (sendingResult.cancelled) {
|
|
341
|
+
logger.info(`outbound: cancelled by message_sending hook to=${ctx.To}`);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
text = sendingResult.text;
|
|
345
|
+
|
|
333
346
|
try {
|
|
334
347
|
if (mediaUrl) {
|
|
335
348
|
let filePath: string;
|
|
336
349
|
if (!mediaUrl.includes("://") || mediaUrl.startsWith("file://")) {
|
|
337
|
-
// Local path: absolute, relative, or file:// URL
|
|
338
350
|
if (mediaUrl.startsWith("file://")) {
|
|
339
351
|
filePath = new URL(mediaUrl).pathname;
|
|
340
352
|
} else if (!path.isAbsolute(mediaUrl)) {
|
|
@@ -357,6 +369,7 @@ export async function processOneMessage(
|
|
|
357
369
|
token: deps.token,
|
|
358
370
|
contextToken,
|
|
359
371
|
}});
|
|
372
|
+
emitWeixinMessageSent({ to: ctx.To, content: text, success: true, accountId: deps.accountId });
|
|
360
373
|
logger.info(`outbound: text sent to=${ctx.To}`);
|
|
361
374
|
return;
|
|
362
375
|
}
|
|
@@ -367,6 +380,7 @@ export async function processOneMessage(
|
|
|
367
380
|
opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken },
|
|
368
381
|
cdnBaseUrl: deps.cdnBaseUrl,
|
|
369
382
|
});
|
|
383
|
+
emitWeixinMessageSent({ to: ctx.To, content: text, success: true, accountId: deps.accountId });
|
|
370
384
|
logger.info(`outbound: media sent OK to=${ctx.To}`);
|
|
371
385
|
} else {
|
|
372
386
|
logger.debug(`outbound: sending text message to=${ctx.To}`);
|
|
@@ -375,9 +389,11 @@ export async function processOneMessage(
|
|
|
375
389
|
token: deps.token,
|
|
376
390
|
contextToken,
|
|
377
391
|
}});
|
|
392
|
+
emitWeixinMessageSent({ to: ctx.To, content: text, success: true, accountId: deps.accountId });
|
|
378
393
|
logger.info(`outbound: text sent OK to=${ctx.To}`);
|
|
379
394
|
}
|
|
380
395
|
} catch (err) {
|
|
396
|
+
emitWeixinMessageSent({ to: ctx.To, content: text, success: false, error: String(err), accountId: deps.accountId });
|
|
381
397
|
logger.error(
|
|
382
398
|
`outbound: FAILED to=${ctx.To} mediaUrl=${mediaUrl ?? "none"} err=${String(err)} stack=${(err as Error).stack ?? ""}`,
|
|
383
399
|
);
|