@xpert-ai/plugin-community-wechat 0.1.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/README.md +353 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +139 -0
- package/dist/lib/constants.d.ts +23 -0
- package/dist/lib/constants.js +23 -0
- package/dist/lib/conversation-user-key.d.ts +13 -0
- package/dist/lib/conversation-user-key.js +28 -0
- package/dist/lib/conversation.service.d.ts +215 -0
- package/dist/lib/conversation.service.js +1179 -0
- package/dist/lib/decorators.d.ts +2 -0
- package/dist/lib/decorators.js +3 -0
- package/dist/lib/entities/index.d.ts +4 -0
- package/dist/lib/entities/index.js +4 -0
- package/dist/lib/entities/wechat-personal-account.entity.d.ts +19 -0
- package/dist/lib/entities/wechat-personal-account.entity.js +83 -0
- package/dist/lib/entities/wechat-personal-conversation-binding.entity.d.ts +14 -0
- package/dist/lib/entities/wechat-personal-conversation-binding.entity.js +65 -0
- package/dist/lib/entities/wechat-personal-message-log.entity.d.ts +27 -0
- package/dist/lib/entities/wechat-personal-message-log.entity.js +108 -0
- package/dist/lib/entities/wechat-personal-trigger-binding.entity.d.ts +17 -0
- package/dist/lib/entities/wechat-personal-trigger-binding.entity.js +71 -0
- package/dist/lib/handoff/index.d.ts +4 -0
- package/dist/lib/handoff/index.js +4 -0
- package/dist/lib/handoff/wechat-personal-chat-callback.processor.d.ts +26 -0
- package/dist/lib/handoff/wechat-personal-chat-callback.processor.js +312 -0
- package/dist/lib/handoff/wechat-personal-chat-dispatch.service.d.ts +26 -0
- package/dist/lib/handoff/wechat-personal-chat-dispatch.service.js +187 -0
- package/dist/lib/handoff/wechat-personal-chat-run-state.service.d.ts +21 -0
- package/dist/lib/handoff/wechat-personal-chat-run-state.service.js +39 -0
- package/dist/lib/handoff/wechat-personal-chat.types.d.ts +69 -0
- package/dist/lib/handoff/wechat-personal-chat.types.js +2 -0
- package/dist/lib/message.d.ts +49 -0
- package/dist/lib/message.js +64 -0
- package/dist/lib/remote-components/wechat-personal-workbench/app.js +1831 -0
- package/dist/lib/tokens.d.ts +1 -0
- package/dist/lib/tokens.js +1 -0
- package/dist/lib/types.d.ts +48 -0
- package/dist/lib/types.js +365 -0
- package/dist/lib/views/wechat-personal-view.provider.d.ts +17 -0
- package/dist/lib/views/wechat-personal-view.provider.js +441 -0
- package/dist/lib/wechat-personal-channel.strategy.d.ts +33 -0
- package/dist/lib/wechat-personal-channel.strategy.js +197 -0
- package/dist/lib/wechat-personal-integration.strategy.d.ts +56 -0
- package/dist/lib/wechat-personal-integration.strategy.js +217 -0
- package/dist/lib/wechat-personal.client.d.ts +29 -0
- package/dist/lib/wechat-personal.client.js +146 -0
- package/dist/lib/wechat-personal.controller.d.ts +50 -0
- package/dist/lib/wechat-personal.controller.js +270 -0
- package/dist/lib/wechat-personal.middleware.d.ts +20 -0
- package/dist/lib/wechat-personal.middleware.js +267 -0
- package/dist/lib/wechat-personal.plugin.d.ts +2 -0
- package/dist/lib/wechat-personal.plugin.js +58 -0
- package/dist/lib/wechat-personal.templates.d.ts +2 -0
- package/dist/lib/wechat-personal.templates.js +100 -0
- package/dist/lib/workflow/index.d.ts +5 -0
- package/dist/lib/workflow/index.js +5 -0
- package/dist/lib/workflow/wechat-personal-trigger-aggregation.service.d.ts +10 -0
- package/dist/lib/workflow/wechat-personal-trigger-aggregation.service.js +39 -0
- package/dist/lib/workflow/wechat-personal-trigger-aggregation.types.d.ts +30 -0
- package/dist/lib/workflow/wechat-personal-trigger-aggregation.types.js +2 -0
- package/dist/lib/workflow/wechat-personal-trigger-flush.processor.d.ts +8 -0
- package/dist/lib/workflow/wechat-personal-trigger-flush.processor.js +39 -0
- package/dist/lib/workflow/wechat-personal-trigger.strategy.d.ts +65 -0
- package/dist/lib/workflow/wechat-personal-trigger.strategy.js +511 -0
- package/dist/lib/workflow/wechat-personal-trigger.types.d.ts +10 -0
- package/dist/lib/workflow/wechat-personal-trigger.types.js +2 -0
- package/dist/xpert-wechat-personal-admin-assistant.yaml +103 -0
- package/dist/xpert-wechat-personal-user-assistant.yaml +127 -0
- package/package.json +79 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { Injectable } from '@nestjs/common';
|
|
11
|
+
import { readFile } from 'fs/promises';
|
|
12
|
+
import { createRequire } from 'module';
|
|
13
|
+
import { dirname, join } from 'path';
|
|
14
|
+
import { fileURLToPath } from 'url';
|
|
15
|
+
import { renderRemoteReactIframeHtml, ViewExtensionProvider, getErrorMessage } from '@xpert-ai/plugin-sdk';
|
|
16
|
+
import { AGENT_WORKBENCH_FIXED_SLOT, AGENT_WORKBENCH_MAIN_SLOT, WECHAT_PERSONAL_FEATURE, WECHAT_PERSONAL_ICON, WECHAT_PERSONAL_PLUGIN_NAME, WECHAT_PERSONAL_PROVIDER_KEY, WECHAT_PERSONAL_REMOTE_ENTRY_KEY, WECHAT_PERSONAL_VIEW_KEY, WECHAT_PERSONAL_VIEW_PROVIDER_KEY } from '../constants.js';
|
|
17
|
+
import { WechatPersonalConversationService } from '../conversation.service.js';
|
|
18
|
+
import { WechatPersonalChannelStrategy } from '../wechat-personal-channel.strategy.js';
|
|
19
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
20
|
+
const moduleDir = dirname(__filename);
|
|
21
|
+
const requireFromHere = createRequire(__filename);
|
|
22
|
+
const INTEGRATION_DETAIL_MAIN_TABS_SLOT = 'detail.main_tabs';
|
|
23
|
+
const text = (en_US, zh_Hans) => ({ en_US, zh_Hans });
|
|
24
|
+
const WECHAT_PERSONAL_VIEW_ICON = {
|
|
25
|
+
type: 'svg',
|
|
26
|
+
value: WECHAT_PERSONAL_ICON,
|
|
27
|
+
alt: 'Personal WeChat'
|
|
28
|
+
};
|
|
29
|
+
// plugin-sdk 3.10 still types view icons as strings, while contracts 3.11 accepts IconDefinition.
|
|
30
|
+
const WECHAT_PERSONAL_VIEW_ICON_FOR_SDK = WECHAT_PERSONAL_VIEW_ICON;
|
|
31
|
+
let WechatPersonalViewProvider = class WechatPersonalViewProvider {
|
|
32
|
+
constructor(conversationService, wechatChannel) {
|
|
33
|
+
this.conversationService = conversationService;
|
|
34
|
+
this.wechatChannel = wechatChannel;
|
|
35
|
+
}
|
|
36
|
+
supports(context) {
|
|
37
|
+
if (context.hostType === 'agent') {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
if (context.hostType !== 'integration') {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
return [getStringProperty(context.hostSnapshot, 'provider'), getStringProperty(context.hostSnapshot, 'type')].some((value) => value === WECHAT_PERSONAL_PROVIDER_KEY);
|
|
44
|
+
}
|
|
45
|
+
getViewManifests(context, slot) {
|
|
46
|
+
if (!isSupportedSlot(context, slot)) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
const isAgentFixedWorkbench = context.hostType === 'agent' && slot === AGENT_WORKBENCH_FIXED_SLOT;
|
|
50
|
+
const requiresFeatureActivation = context.hostType === 'agent';
|
|
51
|
+
return [
|
|
52
|
+
{
|
|
53
|
+
key: WECHAT_PERSONAL_VIEW_KEY,
|
|
54
|
+
title: text('Personal WeChat', '个人微信'),
|
|
55
|
+
description: text('Manage wx2.0 accounts, conversations, message logs, callbacks, and dispatch settings.', '管理 wx2.0 账号、会话、消息日志、回调和派发配置。'),
|
|
56
|
+
icon: WECHAT_PERSONAL_VIEW_ICON_FOR_SDK,
|
|
57
|
+
hostType: context.hostType,
|
|
58
|
+
slot,
|
|
59
|
+
order: 40,
|
|
60
|
+
refreshable: true,
|
|
61
|
+
...(requiresFeatureActivation
|
|
62
|
+
? {
|
|
63
|
+
activation: {
|
|
64
|
+
requiredFeatures: [WECHAT_PERSONAL_FEATURE]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
: {}),
|
|
68
|
+
...(isAgentFixedWorkbench
|
|
69
|
+
? {
|
|
70
|
+
workbench: {
|
|
71
|
+
fixed: true,
|
|
72
|
+
menu: {
|
|
73
|
+
enabled: true,
|
|
74
|
+
label: text('Personal WeChat', '个人微信'),
|
|
75
|
+
order: 40,
|
|
76
|
+
icon: WECHAT_PERSONAL_VIEW_ICON_FOR_SDK
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
: {}),
|
|
81
|
+
source: {
|
|
82
|
+
provider: WECHAT_PERSONAL_VIEW_PROVIDER_KEY,
|
|
83
|
+
plugin: WECHAT_PERSONAL_PLUGIN_NAME
|
|
84
|
+
},
|
|
85
|
+
view: {
|
|
86
|
+
type: 'remote_component',
|
|
87
|
+
runtime: 'react',
|
|
88
|
+
protocolVersion: 1,
|
|
89
|
+
component: {
|
|
90
|
+
isolation: 'iframe',
|
|
91
|
+
entry: WECHAT_PERSONAL_REMOTE_ENTRY_KEY
|
|
92
|
+
},
|
|
93
|
+
dataSource: {
|
|
94
|
+
mode: 'platform'
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
dataSource: {
|
|
98
|
+
mode: 'platform',
|
|
99
|
+
querySchema: {
|
|
100
|
+
supportsPagination: true,
|
|
101
|
+
supportsSearch: true,
|
|
102
|
+
supportsParameters: true,
|
|
103
|
+
defaultPageSize: 30
|
|
104
|
+
},
|
|
105
|
+
cache: {
|
|
106
|
+
enabled: false
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
actions: [
|
|
110
|
+
{
|
|
111
|
+
key: 'refresh',
|
|
112
|
+
label: text('Refresh', '刷新'),
|
|
113
|
+
icon: 'ri-refresh-line',
|
|
114
|
+
placement: 'toolbar',
|
|
115
|
+
actionType: 'refresh'
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
key: 'register_callback',
|
|
119
|
+
label: text('Register Callback', '注册回调'),
|
|
120
|
+
icon: 'ri-link',
|
|
121
|
+
actionType: 'invoke'
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
key: 'restart_conversation',
|
|
125
|
+
label: text('Restart Conversation', '重置会话'),
|
|
126
|
+
icon: 'ri-restart-line',
|
|
127
|
+
actionType: 'invoke'
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
key: 'send_text',
|
|
131
|
+
label: text('Send Text', '发送文本'),
|
|
132
|
+
icon: 'ri-send-plane-line',
|
|
133
|
+
actionType: 'invoke'
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
key: 'resend_message',
|
|
137
|
+
label: text('Resend AI Reply', '重发 AI 回复'),
|
|
138
|
+
icon: 'ri-repeat-line',
|
|
139
|
+
actionType: 'invoke'
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
key: 'set_account_enabled',
|
|
143
|
+
label: text('Toggle Account', '启停账号'),
|
|
144
|
+
icon: 'ri-toggle-line',
|
|
145
|
+
actionType: 'invoke'
|
|
146
|
+
}
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
];
|
|
150
|
+
}
|
|
151
|
+
async getRemoteComponentEntry(_context, viewKey, component) {
|
|
152
|
+
if (viewKey !== WECHAT_PERSONAL_VIEW_KEY || component.entry !== WECHAT_PERSONAL_REMOTE_ENTRY_KEY) {
|
|
153
|
+
return {
|
|
154
|
+
html: '<!doctype html><html><body>Unsupported remote component entry.</body></html>',
|
|
155
|
+
contentType: 'text/html; charset=utf-8'
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const appScript = await readFile(join(moduleDir, '..', 'remote-components', WECHAT_PERSONAL_REMOTE_ENTRY_KEY, 'app.js'), 'utf8');
|
|
159
|
+
const react = await readPackageFile('react', 'umd/react.production.min.js');
|
|
160
|
+
const reactDom = await readPackageFile('react-dom', 'umd/react-dom.production.min.js');
|
|
161
|
+
return {
|
|
162
|
+
html: renderRemoteReactIframeHtml({
|
|
163
|
+
title: 'Personal WeChat Workbench',
|
|
164
|
+
lang: 'zh-Hans',
|
|
165
|
+
reactUmd: react,
|
|
166
|
+
reactDomUmd: reactDom,
|
|
167
|
+
appScript
|
|
168
|
+
}),
|
|
169
|
+
contentType: 'text/html; charset=utf-8'
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
async getViewData(context, viewKey, query) {
|
|
173
|
+
if (viewKey !== WECHAT_PERSONAL_VIEW_KEY) {
|
|
174
|
+
return {};
|
|
175
|
+
}
|
|
176
|
+
const parameters = normalizeViewParameters(query.parameters);
|
|
177
|
+
const integrationId = await this.resolveIntegrationId(context, parameters);
|
|
178
|
+
const table = getTableKey(parameters);
|
|
179
|
+
const tableQuery = getTableQuery(query, parameters);
|
|
180
|
+
if (!integrationId) {
|
|
181
|
+
if (context.hostType === 'agent') {
|
|
182
|
+
if (table) {
|
|
183
|
+
return {
|
|
184
|
+
scope: 'organization',
|
|
185
|
+
integrationId: null,
|
|
186
|
+
tableKey: table,
|
|
187
|
+
table: await this.conversationService.getOrganizationWorkbenchTableData(table, tableQuery)
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
return this.conversationService.getOrganizationWorkbenchData({
|
|
191
|
+
search: query.search,
|
|
192
|
+
page: query.page,
|
|
193
|
+
pageSize: query.pageSize
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
missingIntegration: true,
|
|
198
|
+
message: text('Select a personal WeChat integration to view runtime data.', '请选择个人微信集成后查看运行数据。')
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
if (table) {
|
|
202
|
+
return {
|
|
203
|
+
scope: 'integration',
|
|
204
|
+
integrationId,
|
|
205
|
+
tableKey: table,
|
|
206
|
+
table: await this.conversationService.getWorkbenchTableData(integrationId, table, tableQuery)
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
return this.conversationService.getWorkbenchData(integrationId, {
|
|
210
|
+
search: query.search,
|
|
211
|
+
page: query.page,
|
|
212
|
+
pageSize: query.pageSize
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
async executeViewAction(context, viewKey, actionKey, request) {
|
|
216
|
+
if (viewKey !== WECHAT_PERSONAL_VIEW_KEY) {
|
|
217
|
+
return failure('Unsupported view', '不支持的视图');
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
if (actionKey === 'refresh') {
|
|
221
|
+
return success('Personal WeChat view refreshed', '个人微信视图已刷新');
|
|
222
|
+
}
|
|
223
|
+
const integrationId = await this.resolveIntegrationId(context, request.parameters, request.input);
|
|
224
|
+
if (!integrationId) {
|
|
225
|
+
return failure('Missing integration id', '缺少个人微信集成标识');
|
|
226
|
+
}
|
|
227
|
+
if (actionKey === 'restart_conversation') {
|
|
228
|
+
const targetId = getStringInput(request.input, 'bindingId') || getStringInput(request.input, 'id') || request.targetId;
|
|
229
|
+
if (!targetId) {
|
|
230
|
+
return failure('Missing conversation target', '缺少可重置的会话目标');
|
|
231
|
+
}
|
|
232
|
+
await this.conversationService.restartConversationBinding(integrationId, String(targetId));
|
|
233
|
+
return success('Conversation reset', '会话已重置');
|
|
234
|
+
}
|
|
235
|
+
if (actionKey === 'register_callback') {
|
|
236
|
+
const integration = await this.wechatChannel.readIntegrationById(integrationId);
|
|
237
|
+
if (!integration) {
|
|
238
|
+
return failure('Integration not found', '未找到个人微信集成');
|
|
239
|
+
}
|
|
240
|
+
const uuid = requireStringInput(request.input, 'uuid', 'Account uuid is required.');
|
|
241
|
+
const callbackConfig = this.conversationService.buildCallbackConfig(integration.id, integration.options?.callbackSecret);
|
|
242
|
+
const result = await this.wechatChannel.registerCallback({
|
|
243
|
+
integrationId,
|
|
244
|
+
uuid,
|
|
245
|
+
callbackUrl: getStringInput(request.input, 'callbackUrl') || callbackConfig.webhookUrl,
|
|
246
|
+
enabled: getBooleanInput(request.input, 'enabled', true)
|
|
247
|
+
});
|
|
248
|
+
if (!result.success) {
|
|
249
|
+
return failure(result.error || 'Register callback failed', result.error || '注册回调失败');
|
|
250
|
+
}
|
|
251
|
+
return success('Callback registered', '回调已注册');
|
|
252
|
+
}
|
|
253
|
+
if (actionKey === 'send_text') {
|
|
254
|
+
const result = await this.wechatChannel.sendTextByIntegrationId(integrationId, {
|
|
255
|
+
uuid: requireStringInput(request.input, 'uuid', 'Account uuid is required.'),
|
|
256
|
+
contactId: requireStringInput(request.input, 'contactId', 'Contact id is required.'),
|
|
257
|
+
content: requireStringInput(request.input, 'content', 'Text content is required.'),
|
|
258
|
+
atUsers: getStringArrayInput(request.input, 'atUsers')
|
|
259
|
+
});
|
|
260
|
+
if (!result.success) {
|
|
261
|
+
return failure(result.error || 'Send text failed', result.error || '发送文本失败');
|
|
262
|
+
}
|
|
263
|
+
return success('Text sent', '文本已发送');
|
|
264
|
+
}
|
|
265
|
+
if (actionKey === 'resend_message') {
|
|
266
|
+
const result = await this.conversationService.resendOutboundMessage(integrationId, request.targetId);
|
|
267
|
+
if (!result.success) {
|
|
268
|
+
return failure(result.error || 'Resend failed', result.error || '重发失败');
|
|
269
|
+
}
|
|
270
|
+
return success('AI reply resent', 'AI 回复已重发');
|
|
271
|
+
}
|
|
272
|
+
if (actionKey === 'set_account_enabled') {
|
|
273
|
+
await this.conversationService.setAccountEnabled(integrationId, requireStringInput(request.input, 'uuid', 'Account uuid is required.'), getBooleanInput(request.input, 'enabled', true));
|
|
274
|
+
return success('Account setting updated', '账号接入设置已更新');
|
|
275
|
+
}
|
|
276
|
+
return failure('Unsupported action', '不支持的操作');
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
const message = getErrorMessage(error);
|
|
280
|
+
return {
|
|
281
|
+
success: false,
|
|
282
|
+
message: text(message, message)
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
async resolveIntegrationId(context, parameters, input) {
|
|
287
|
+
const explicitIntegrationId = getIntegrationId(context, parameters, input);
|
|
288
|
+
if (explicitIntegrationId) {
|
|
289
|
+
return explicitIntegrationId;
|
|
290
|
+
}
|
|
291
|
+
if (context.hostType !== 'agent') {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
const xpertId = getAgentXpertId(context);
|
|
295
|
+
if (!xpertId) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
return this.conversationService.getBoundIntegrationIdForXpert(xpertId);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
WechatPersonalViewProvider = __decorate([
|
|
302
|
+
Injectable(),
|
|
303
|
+
ViewExtensionProvider(WECHAT_PERSONAL_VIEW_PROVIDER_KEY),
|
|
304
|
+
__metadata("design:paramtypes", [WechatPersonalConversationService,
|
|
305
|
+
WechatPersonalChannelStrategy])
|
|
306
|
+
], WechatPersonalViewProvider);
|
|
307
|
+
export { WechatPersonalViewProvider };
|
|
308
|
+
async function readPackageFile(packageName, relativePath) {
|
|
309
|
+
const packageRoot = dirname(requireFromHere.resolve(`${packageName}/package.json`));
|
|
310
|
+
return readFile(join(packageRoot, relativePath), 'utf8');
|
|
311
|
+
}
|
|
312
|
+
function isSupportedSlot(context, slot) {
|
|
313
|
+
if (context.hostType === 'integration') {
|
|
314
|
+
return slot === INTEGRATION_DETAIL_MAIN_TABS_SLOT;
|
|
315
|
+
}
|
|
316
|
+
if (context.hostType === 'agent') {
|
|
317
|
+
return slot === AGENT_WORKBENCH_FIXED_SLOT || slot === AGENT_WORKBENCH_MAIN_SLOT;
|
|
318
|
+
}
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
function normalizeViewParameters(parameters) {
|
|
322
|
+
if (!parameters) {
|
|
323
|
+
return {};
|
|
324
|
+
}
|
|
325
|
+
if (typeof parameters === 'string') {
|
|
326
|
+
return parseJsonObject(parameters);
|
|
327
|
+
}
|
|
328
|
+
if (typeof parameters === 'object' && !Array.isArray(parameters)) {
|
|
329
|
+
return parameters;
|
|
330
|
+
}
|
|
331
|
+
return {};
|
|
332
|
+
}
|
|
333
|
+
function getTableKey(parameters) {
|
|
334
|
+
const value = getStringProperty(parameters, 'table');
|
|
335
|
+
return value === 'accounts' || value === 'conversations' || value === 'messages' || value === 'logs' ? value : null;
|
|
336
|
+
}
|
|
337
|
+
function getTableQuery(query, parameters = normalizeViewParameters(query.parameters)) {
|
|
338
|
+
const rawFilters = parameters.filters;
|
|
339
|
+
const filtersFromJson = parseJsonObject(getStringProperty(parameters, 'filtersJson'));
|
|
340
|
+
return {
|
|
341
|
+
search: query.search,
|
|
342
|
+
page: query.page,
|
|
343
|
+
pageSize: query.pageSize,
|
|
344
|
+
filters: rawFilters && typeof rawFilters === 'object' && !Array.isArray(rawFilters)
|
|
345
|
+
? rawFilters
|
|
346
|
+
: filtersFromJson
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function parseJsonObject(value) {
|
|
350
|
+
if (!value) {
|
|
351
|
+
return {};
|
|
352
|
+
}
|
|
353
|
+
try {
|
|
354
|
+
const parsed = JSON.parse(value);
|
|
355
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
return {};
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function getIntegrationId(context, parameters, input) {
|
|
362
|
+
if (context.hostType === 'integration') {
|
|
363
|
+
return context.hostId || null;
|
|
364
|
+
}
|
|
365
|
+
return (getStringProperty(parameters, 'integrationId') ||
|
|
366
|
+
getStringInput(input, 'integrationId') ||
|
|
367
|
+
getStringProperty(context.hostSnapshot, 'integrationId') ||
|
|
368
|
+
null);
|
|
369
|
+
}
|
|
370
|
+
function getStringProperty(record, key) {
|
|
371
|
+
if (!record || typeof record !== 'object') {
|
|
372
|
+
return '';
|
|
373
|
+
}
|
|
374
|
+
const rawValue = record[key];
|
|
375
|
+
const value = Array.isArray(rawValue) ? rawValue[0] : rawValue;
|
|
376
|
+
if (typeof value === 'string') {
|
|
377
|
+
return value.trim();
|
|
378
|
+
}
|
|
379
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
380
|
+
return String(value);
|
|
381
|
+
}
|
|
382
|
+
return '';
|
|
383
|
+
}
|
|
384
|
+
function getAgentXpertId(context) {
|
|
385
|
+
if (context.hostType !== 'agent') {
|
|
386
|
+
return '';
|
|
387
|
+
}
|
|
388
|
+
return getStringProperty(context, 'hostId') || getStringProperty(context.hostSnapshot, 'id');
|
|
389
|
+
}
|
|
390
|
+
function getStringInput(input, key) {
|
|
391
|
+
return getStringProperty(input, key);
|
|
392
|
+
}
|
|
393
|
+
function requireStringInput(input, key, error) {
|
|
394
|
+
const value = getStringInput(input, key);
|
|
395
|
+
if (!value) {
|
|
396
|
+
throw new Error(error);
|
|
397
|
+
}
|
|
398
|
+
return value;
|
|
399
|
+
}
|
|
400
|
+
function getBooleanInput(input, key, defaultValue) {
|
|
401
|
+
if (!input || typeof input !== 'object') {
|
|
402
|
+
return defaultValue;
|
|
403
|
+
}
|
|
404
|
+
const value = input[key];
|
|
405
|
+
if (typeof value === 'boolean') {
|
|
406
|
+
return value;
|
|
407
|
+
}
|
|
408
|
+
if (typeof value === 'string') {
|
|
409
|
+
return ['true', '1', 'yes'].includes(value.trim().toLowerCase());
|
|
410
|
+
}
|
|
411
|
+
return defaultValue;
|
|
412
|
+
}
|
|
413
|
+
function getStringArrayInput(input, key) {
|
|
414
|
+
if (!input || typeof input !== 'object') {
|
|
415
|
+
return [];
|
|
416
|
+
}
|
|
417
|
+
const value = input[key];
|
|
418
|
+
if (Array.isArray(value)) {
|
|
419
|
+
return value.filter((item) => typeof item === 'string' && Boolean(item.trim()));
|
|
420
|
+
}
|
|
421
|
+
if (typeof value === 'string') {
|
|
422
|
+
return value
|
|
423
|
+
.split(/[,\n,]/)
|
|
424
|
+
.map((item) => item.trim())
|
|
425
|
+
.filter(Boolean);
|
|
426
|
+
}
|
|
427
|
+
return [];
|
|
428
|
+
}
|
|
429
|
+
function success(en_US, zh_Hans) {
|
|
430
|
+
return {
|
|
431
|
+
success: true,
|
|
432
|
+
message: text(en_US, zh_Hans),
|
|
433
|
+
refresh: true
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
function failure(en_US, zh_Hans) {
|
|
437
|
+
return {
|
|
438
|
+
success: false,
|
|
439
|
+
message: text(en_US, zh_Hans)
|
|
440
|
+
};
|
|
441
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { IChatChannel, TChatChannelCapabilities, TChatChannelMeta, TChatContext, TChatEventContext, TChatEventHandlers, TChatInboundMessage, TChatSendResult, type PluginContext } from '@xpert-ai/plugin-sdk';
|
|
2
|
+
import { IIntegration } from '@xpert-ai/contracts';
|
|
3
|
+
import { TIntegrationWechatPersonalOptions, WechatPersonalInboundEvent } from './types.js';
|
|
4
|
+
import { WechatPersonalClient } from './wechat-personal.client.js';
|
|
5
|
+
export declare class WechatPersonalChannelStrategy implements IChatChannel<TIntegrationWechatPersonalOptions, WechatPersonalInboundEvent> {
|
|
6
|
+
private readonly client;
|
|
7
|
+
private readonly pluginContext;
|
|
8
|
+
private readonly logger;
|
|
9
|
+
private _integrationPermissionService;
|
|
10
|
+
constructor(client: WechatPersonalClient, pluginContext: PluginContext);
|
|
11
|
+
private get integrationPermissionService();
|
|
12
|
+
meta: TChatChannelMeta;
|
|
13
|
+
capabilities: TChatChannelCapabilities;
|
|
14
|
+
readIntegrationById(id: string): Promise<IIntegration<TIntegrationWechatPersonalOptions> | null>;
|
|
15
|
+
createEventHandler(ctx: TChatEventContext<TIntegrationWechatPersonalOptions>, handlers: TChatEventHandlers): (req: any, res: any, next?: any) => Promise<void>;
|
|
16
|
+
normalizeWebhookEvent(payload: unknown): WechatPersonalInboundEvent | null;
|
|
17
|
+
parseInboundMessage(event: WechatPersonalInboundEvent, _ctx: TChatEventContext<TIntegrationWechatPersonalOptions>): TChatInboundMessage | null;
|
|
18
|
+
sendText(ctx: TChatContext, content: string): Promise<TChatSendResult>;
|
|
19
|
+
sendTextByIntegrationId(integrationId: string, params: {
|
|
20
|
+
uuid?: string | null;
|
|
21
|
+
contactId?: string | null;
|
|
22
|
+
content: string;
|
|
23
|
+
atUsers?: string[] | null;
|
|
24
|
+
}): Promise<TChatSendResult>;
|
|
25
|
+
registerCallback(params: {
|
|
26
|
+
integrationId: string;
|
|
27
|
+
uuid: string;
|
|
28
|
+
callbackUrl: string;
|
|
29
|
+
enabled?: boolean;
|
|
30
|
+
}): Promise<TChatSendResult>;
|
|
31
|
+
private readIntegration;
|
|
32
|
+
private resolveMentions;
|
|
33
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
var WechatPersonalChannelStrategy_1;
|
|
14
|
+
import { randomUUID } from 'crypto';
|
|
15
|
+
import { Inject, Injectable, Logger } from '@nestjs/common';
|
|
16
|
+
import { ChatChannel, INTEGRATION_PERMISSION_SERVICE_TOKEN } from '@xpert-ai/plugin-sdk';
|
|
17
|
+
import { WECHAT_PERSONAL_CHANNEL_TYPE, WECHAT_PERSONAL_ICON } from './constants.js';
|
|
18
|
+
import { WECHAT_PERSONAL_PLUGIN_CONTEXT } from './tokens.js';
|
|
19
|
+
import { normalizeString, normalizeWechatPersonalInboundPayload } from './types.js';
|
|
20
|
+
import { WechatPersonalClient } from './wechat-personal.client.js';
|
|
21
|
+
const DEFAULT_TEXT_LIMIT = 4000;
|
|
22
|
+
let WechatPersonalChannelStrategy = WechatPersonalChannelStrategy_1 = class WechatPersonalChannelStrategy {
|
|
23
|
+
constructor(client, pluginContext) {
|
|
24
|
+
this.client = client;
|
|
25
|
+
this.pluginContext = pluginContext;
|
|
26
|
+
this.logger = new Logger(WechatPersonalChannelStrategy_1.name);
|
|
27
|
+
this.meta = {
|
|
28
|
+
type: WECHAT_PERSONAL_CHANNEL_TYPE,
|
|
29
|
+
label: '个人微信 / WeChat Personal',
|
|
30
|
+
description: 'wx2.0 personal WeChat webhook and sendtext bridge',
|
|
31
|
+
icon: WECHAT_PERSONAL_ICON,
|
|
32
|
+
configSchema: {
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: {
|
|
35
|
+
baseUrl: { type: 'string', description: 'wx2.0 HTTP base URL' },
|
|
36
|
+
apiVersion: { type: 'string', description: 'wx2.0 v2 API prefix, default /v1/' },
|
|
37
|
+
apiToken: { type: 'string', description: 'Optional wx2.0 token header' },
|
|
38
|
+
timeoutMs: { type: 'number', description: 'Outbound wx2.0 request timeout in milliseconds' }
|
|
39
|
+
},
|
|
40
|
+
required: ['baseUrl']
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
this.capabilities = {
|
|
44
|
+
markdown: false,
|
|
45
|
+
card: false,
|
|
46
|
+
cardAction: false,
|
|
47
|
+
updateMessage: false,
|
|
48
|
+
mention: true,
|
|
49
|
+
group: true,
|
|
50
|
+
thread: false,
|
|
51
|
+
media: false,
|
|
52
|
+
textChunkLimit: DEFAULT_TEXT_LIMIT,
|
|
53
|
+
streamingUpdate: false
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
get integrationPermissionService() {
|
|
57
|
+
if (!this._integrationPermissionService) {
|
|
58
|
+
this._integrationPermissionService = this.pluginContext.resolve(INTEGRATION_PERMISSION_SERVICE_TOKEN);
|
|
59
|
+
}
|
|
60
|
+
return this._integrationPermissionService;
|
|
61
|
+
}
|
|
62
|
+
async readIntegrationById(id) {
|
|
63
|
+
if (!id) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return this.integrationPermissionService.read(id, {
|
|
67
|
+
relations: ['tenant']
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
createEventHandler(ctx, handlers) {
|
|
71
|
+
const eventHandlers = handlers ?? {};
|
|
72
|
+
return async (req, res) => {
|
|
73
|
+
const event = this.normalizeWebhookEvent(req?.body);
|
|
74
|
+
if (!event) {
|
|
75
|
+
this.logger.debug(`[wechat-personal-event] ignore invalid payload integration=${ctx.integration.id}`);
|
|
76
|
+
res.status(200).send('success');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const message = this.parseInboundMessage(event, ctx);
|
|
80
|
+
if (!message) {
|
|
81
|
+
this.logger.debug(`[wechat-personal-event] ignore non-message integration=${ctx.integration.id} uuid=${event.uuid || 'n/a'}`);
|
|
82
|
+
res.status(200).send('success');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (message.chatType === 'group' && message.mentions?.length) {
|
|
86
|
+
await eventHandlers.onMention?.(message, ctx);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
await eventHandlers.onMessage?.(message, ctx);
|
|
90
|
+
}
|
|
91
|
+
res.status(200).send('success');
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
normalizeWebhookEvent(payload) {
|
|
95
|
+
return normalizeWechatPersonalInboundPayload(payload);
|
|
96
|
+
}
|
|
97
|
+
parseInboundMessage(event, _ctx) {
|
|
98
|
+
const chatId = normalizeString(event.contactId);
|
|
99
|
+
const senderId = normalizeString(event.senderId) || chatId;
|
|
100
|
+
if (!chatId || !senderId) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
messageId: event.messageId || randomUUID(),
|
|
105
|
+
chatId,
|
|
106
|
+
chatType: event.chatType,
|
|
107
|
+
senderId,
|
|
108
|
+
senderName: event.senderName,
|
|
109
|
+
content: event.content,
|
|
110
|
+
contentType: 'text',
|
|
111
|
+
mentions: event.chatType === 'group' ? this.resolveMentions(event) : undefined,
|
|
112
|
+
timestamp: event.timestamp || Date.now(),
|
|
113
|
+
raw: event.rawPayload ?? event.raw ?? event
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async sendText(ctx, content) {
|
|
117
|
+
return this.sendTextByIntegrationId(ctx.integration.id, {
|
|
118
|
+
uuid: normalizeString(ctx.uuid),
|
|
119
|
+
contactId: ctx.chatId,
|
|
120
|
+
content
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
async sendTextByIntegrationId(integrationId, params) {
|
|
124
|
+
const integration = await this.readIntegration(params.uuid ? integrationId : '');
|
|
125
|
+
if (!integration) {
|
|
126
|
+
return {
|
|
127
|
+
success: false,
|
|
128
|
+
error: '个人微信集成不存在或不可访问。'
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const uuid = normalizeString(params.uuid);
|
|
132
|
+
const contactId = normalizeString(params.contactId);
|
|
133
|
+
const content = normalizeString(params.content);
|
|
134
|
+
if (!uuid || !contactId || !content) {
|
|
135
|
+
return {
|
|
136
|
+
success: false,
|
|
137
|
+
error: '发送微信消息缺少 uuid/contactId/content。'
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const result = await this.client.sendText(integration, {
|
|
141
|
+
uuid,
|
|
142
|
+
contactId,
|
|
143
|
+
content,
|
|
144
|
+
atUsers: Array.isArray(params.atUsers) ? params.atUsers.filter(Boolean) : []
|
|
145
|
+
});
|
|
146
|
+
return {
|
|
147
|
+
success: result.success,
|
|
148
|
+
messageId: result.messageId,
|
|
149
|
+
error: result.error
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
async registerCallback(params) {
|
|
153
|
+
const integration = await this.readIntegrationById(params.integrationId);
|
|
154
|
+
if (!integration) {
|
|
155
|
+
return {
|
|
156
|
+
success: false,
|
|
157
|
+
error: '个人微信集成不存在或不可访问。'
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const result = await this.client.registerCallback({
|
|
161
|
+
integration,
|
|
162
|
+
uuid: params.uuid,
|
|
163
|
+
callbackUrl: params.callbackUrl,
|
|
164
|
+
enabled: params.enabled
|
|
165
|
+
});
|
|
166
|
+
return {
|
|
167
|
+
success: result.success,
|
|
168
|
+
messageId: result.messageId,
|
|
169
|
+
error: result.error
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
async readIntegration(integrationId) {
|
|
173
|
+
const id = normalizeString(integrationId);
|
|
174
|
+
if (!id) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
return this.readIntegrationById(id);
|
|
178
|
+
}
|
|
179
|
+
resolveMentions(event) {
|
|
180
|
+
const rawText = JSON.stringify(event.raw || {});
|
|
181
|
+
const ownerWxid = normalizeString(event.ownerWxid);
|
|
182
|
+
if (ownerWxid && rawText.includes(ownerWxid)) {
|
|
183
|
+
return [{ id: ownerWxid }];
|
|
184
|
+
}
|
|
185
|
+
if (/@[^\s]+/.test(`${event.content}\n${event.displayText || ''}`)) {
|
|
186
|
+
return [{ id: 'mentioned' }];
|
|
187
|
+
}
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
WechatPersonalChannelStrategy = WechatPersonalChannelStrategy_1 = __decorate([
|
|
192
|
+
Injectable(),
|
|
193
|
+
ChatChannel(WECHAT_PERSONAL_CHANNEL_TYPE),
|
|
194
|
+
__param(1, Inject(WECHAT_PERSONAL_PLUGIN_CONTEXT)),
|
|
195
|
+
__metadata("design:paramtypes", [WechatPersonalClient, Object])
|
|
196
|
+
], WechatPersonalChannelStrategy);
|
|
197
|
+
export { WechatPersonalChannelStrategy };
|