@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,1179 @@
|
|
|
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 WechatPersonalConversationService_1;
|
|
14
|
+
import { CACHE_MANAGER } from '@nestjs/cache-manager';
|
|
15
|
+
import { Inject, Injectable, Logger } from '@nestjs/common';
|
|
16
|
+
import { InjectRepository } from '@nestjs/typeorm';
|
|
17
|
+
import { INTEGRATION_PERMISSION_SERVICE_TOKEN, RequestContext } from '@xpert-ai/plugin-sdk';
|
|
18
|
+
import { MoreThan, Repository } from 'typeorm';
|
|
19
|
+
import { normalizeConversationKey, parseWechatPersonalConversationUserKey, resolveWechatPersonalConversationUserKey } from './conversation-user-key.js';
|
|
20
|
+
import { summarizePayload, shouldDispatchWechatPersonalMessage } from './types.js';
|
|
21
|
+
import { WECHAT_PERSONAL_PLUGIN_CONTEXT } from './tokens.js';
|
|
22
|
+
import { WECHAT_PERSONAL_PROVIDER_KEY } from './constants.js';
|
|
23
|
+
import { WechatPersonalMessage } from './message.js';
|
|
24
|
+
import { WechatPersonalChannelStrategy } from './wechat-personal-channel.strategy.js';
|
|
25
|
+
import { WechatPersonalAccountEntity, WechatPersonalConversationBindingEntity, WechatPersonalMessageLogEntity } from './entities/index.js';
|
|
26
|
+
import { WechatPersonalTriggerStrategy } from './workflow/wechat-personal-trigger.strategy.js';
|
|
27
|
+
const CACHE_TTL_MS = 10 * 60 * 1000;
|
|
28
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
29
|
+
let WechatPersonalConversationService = WechatPersonalConversationService_1 = class WechatPersonalConversationService {
|
|
30
|
+
constructor(wechatChannel, triggerStrategy, cacheManager, conversationBindingRepository, accountRepository, messageLogRepository, pluginContext) {
|
|
31
|
+
this.wechatChannel = wechatChannel;
|
|
32
|
+
this.triggerStrategy = triggerStrategy;
|
|
33
|
+
this.cacheManager = cacheManager;
|
|
34
|
+
this.conversationBindingRepository = conversationBindingRepository;
|
|
35
|
+
this.accountRepository = accountRepository;
|
|
36
|
+
this.messageLogRepository = messageLogRepository;
|
|
37
|
+
this.pluginContext = pluginContext;
|
|
38
|
+
this.logger = new Logger(WechatPersonalConversationService_1.name);
|
|
39
|
+
}
|
|
40
|
+
get integrationPermissionService() {
|
|
41
|
+
if (!this._integrationPermissionService) {
|
|
42
|
+
this._integrationPermissionService = this.pluginContext.resolve(INTEGRATION_PERMISSION_SERVICE_TOKEN);
|
|
43
|
+
}
|
|
44
|
+
return this._integrationPermissionService;
|
|
45
|
+
}
|
|
46
|
+
async getConversationState(conversationUserKey, xpertId, scopeOverride) {
|
|
47
|
+
const normalizedUserKey = normalizeConversationKey(conversationUserKey);
|
|
48
|
+
const normalizedXpertId = normalizeConversationKey(xpertId);
|
|
49
|
+
if (!normalizedUserKey || !normalizedXpertId) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
const scope = this.resolveTenantScope(scopeOverride);
|
|
53
|
+
const cached = await this.cacheManager.get(this.getConversationCacheKey(normalizedUserKey, normalizedXpertId, scope));
|
|
54
|
+
if (cached && typeof cached === 'object') {
|
|
55
|
+
const conversationId = normalizeConversationKey(cached.conversationId);
|
|
56
|
+
if (conversationId) {
|
|
57
|
+
return {
|
|
58
|
+
conversationId,
|
|
59
|
+
lastActiveAt: this.normalizeDate(cached.lastActiveAt)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const binding = await this.conversationBindingRepository.findOne({
|
|
64
|
+
where: this.scopedWhere({
|
|
65
|
+
conversationUserKey: normalizedUserKey,
|
|
66
|
+
xpertId: normalizedXpertId
|
|
67
|
+
}, scope)
|
|
68
|
+
});
|
|
69
|
+
const conversationId = normalizeConversationKey(binding?.conversationId);
|
|
70
|
+
if (!conversationId) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
const lastActiveAt = this.normalizeDate(binding?.lastActiveAt) ?? this.normalizeDate(binding?.updatedAt);
|
|
74
|
+
await this.cacheConversation(normalizedUserKey, normalizedXpertId, conversationId, lastActiveAt, scope);
|
|
75
|
+
return {
|
|
76
|
+
conversationId,
|
|
77
|
+
lastActiveAt
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
async setConversation(conversationUserKey, xpertId, conversationId, lastActiveAt = new Date(), scopeOverride) {
|
|
81
|
+
const normalizedUserKey = normalizeConversationKey(conversationUserKey);
|
|
82
|
+
const normalizedXpertId = normalizeConversationKey(xpertId);
|
|
83
|
+
const normalizedConversationId = normalizeConversationKey(conversationId);
|
|
84
|
+
if (!normalizedUserKey || !normalizedXpertId || !normalizedConversationId) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const resolvedLastActiveAt = this.normalizeDate(lastActiveAt) ?? new Date();
|
|
88
|
+
const bindingContext = this.resolveBindingContext();
|
|
89
|
+
const scope = this.resolveTenantScope(scopeOverride, bindingContext);
|
|
90
|
+
await this.cacheConversation(normalizedUserKey, normalizedXpertId, normalizedConversationId, resolvedLastActiveAt, scope);
|
|
91
|
+
await this.conversationBindingRepository.upsert({
|
|
92
|
+
conversationUserKey: normalizedUserKey,
|
|
93
|
+
xpertId: normalizedXpertId,
|
|
94
|
+
conversationId: normalizedConversationId,
|
|
95
|
+
lastActiveAt: resolvedLastActiveAt,
|
|
96
|
+
tenantId: scope.tenantId ?? null,
|
|
97
|
+
organizationId: scope.organizationId ?? null,
|
|
98
|
+
createdById: bindingContext.createdById ?? null,
|
|
99
|
+
updatedById: bindingContext.updatedById ?? null
|
|
100
|
+
}, ['conversationUserKey', 'xpertId']);
|
|
101
|
+
}
|
|
102
|
+
async touchConversation(conversationUserKey, xpertId, lastActiveAt = new Date(), scopeOverride) {
|
|
103
|
+
const current = await this.getConversationState(conversationUserKey, xpertId, scopeOverride);
|
|
104
|
+
if (!current?.conversationId) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
await this.setConversation(conversationUserKey, xpertId, current.conversationId, lastActiveAt, scopeOverride);
|
|
108
|
+
}
|
|
109
|
+
async clearConversation(conversationUserKey, xpertId, scopeOverride) {
|
|
110
|
+
const normalizedUserKey = normalizeConversationKey(conversationUserKey);
|
|
111
|
+
const normalizedXpertId = normalizeConversationKey(xpertId);
|
|
112
|
+
if (!normalizedUserKey || !normalizedXpertId) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const scope = this.resolveTenantScope(scopeOverride);
|
|
116
|
+
await this.cacheManager.del(this.getConversationCacheKey(normalizedUserKey, normalizedXpertId, scope));
|
|
117
|
+
await this.conversationBindingRepository.delete(this.scopedWhere({
|
|
118
|
+
conversationUserKey: normalizedUserKey,
|
|
119
|
+
xpertId: normalizedXpertId
|
|
120
|
+
}, scope));
|
|
121
|
+
}
|
|
122
|
+
async restartConversationBinding(integrationId, bindingId) {
|
|
123
|
+
const normalizedIntegrationId = normalizeConversationKey(integrationId);
|
|
124
|
+
const scope = await this.readIntegrationTenantScope(normalizedIntegrationId);
|
|
125
|
+
const binding = await this.conversationBindingRepository.findOne({
|
|
126
|
+
where: this.scopedWhere({
|
|
127
|
+
id: normalizeConversationKey(bindingId)
|
|
128
|
+
}, scope)
|
|
129
|
+
});
|
|
130
|
+
if (!binding) {
|
|
131
|
+
throw new Error('该微信会话不存在或已被重置。');
|
|
132
|
+
}
|
|
133
|
+
const parsed = parseWechatPersonalConversationUserKey(binding.conversationUserKey);
|
|
134
|
+
if (!parsed || parsed.integrationId !== normalizedIntegrationId) {
|
|
135
|
+
throw new Error('该微信会话不属于当前个人微信集成。');
|
|
136
|
+
}
|
|
137
|
+
await this.clearConversation(binding.conversationUserKey, binding.xpertId, scope);
|
|
138
|
+
}
|
|
139
|
+
async handleInboundEvent(event, ctx) {
|
|
140
|
+
const integration = await this.integrationPermissionService.read(ctx.integration.id, {
|
|
141
|
+
relations: ['tenant']
|
|
142
|
+
});
|
|
143
|
+
if (!integration) {
|
|
144
|
+
this.logger.error(`Integration ${ctx.integration.id} not found`);
|
|
145
|
+
return { handled: false, reason: 'integration_not_found' };
|
|
146
|
+
}
|
|
147
|
+
const eventScope = this.resolveTenantScope(integration, ctx);
|
|
148
|
+
const accountState = await this.upsertAccount(integration, event, ctx);
|
|
149
|
+
if (accountState.enabled === false) {
|
|
150
|
+
await this.logInbound(integration, event, 'skipped', {
|
|
151
|
+
error: 'account_disabled'
|
|
152
|
+
});
|
|
153
|
+
return { handled: false, reason: 'account_disabled' };
|
|
154
|
+
}
|
|
155
|
+
const duplicate = await this.isDuplicateInbound(integration.id, event, eventScope);
|
|
156
|
+
const inboundLog = await this.logInbound(integration, event, duplicate ? 'skipped' : 'received', {
|
|
157
|
+
error: duplicate ? 'duplicate_message_id' : undefined
|
|
158
|
+
});
|
|
159
|
+
if (duplicate) {
|
|
160
|
+
return { handled: false, reason: 'duplicate' };
|
|
161
|
+
}
|
|
162
|
+
const binding = await this.triggerStrategy.getBinding(integration.id, eventScope);
|
|
163
|
+
if (!binding?.xpertId) {
|
|
164
|
+
await this.updateLog(inboundLog.id, {
|
|
165
|
+
status: 'skipped',
|
|
166
|
+
error: 'trigger_binding_missing'
|
|
167
|
+
}, eventScope);
|
|
168
|
+
return { handled: false, reason: 'trigger_binding_missing' };
|
|
169
|
+
}
|
|
170
|
+
const dispatchable = shouldDispatchWechatPersonalMessage(event, {
|
|
171
|
+
ignoreSelfMessages: integration.options?.ignoreSelfMessages,
|
|
172
|
+
groupTriggerMode: binding.groupTriggerMode || integration.options?.groupTriggerMode,
|
|
173
|
+
groupKeywords: binding.groupKeywords?.length ? binding.groupKeywords : integration.options?.groupKeywords
|
|
174
|
+
});
|
|
175
|
+
if (!dispatchable) {
|
|
176
|
+
await this.updateLog(inboundLog.id, {
|
|
177
|
+
status: 'skipped',
|
|
178
|
+
xpertId: binding.xpertId,
|
|
179
|
+
error: 'filtered_by_trigger_policy'
|
|
180
|
+
}, eventScope);
|
|
181
|
+
return { handled: false, reason: 'filtered' };
|
|
182
|
+
}
|
|
183
|
+
const executorUserId = this.resolveExecutionUserId(integration);
|
|
184
|
+
const conversationUserKey = resolveWechatPersonalConversationUserKey({
|
|
185
|
+
integrationId: integration.id,
|
|
186
|
+
uuid: event.uuid,
|
|
187
|
+
contactId: event.contactId,
|
|
188
|
+
senderId: event.senderId || event.contactId
|
|
189
|
+
});
|
|
190
|
+
const wechatMessage = new WechatPersonalMessage({
|
|
191
|
+
integrationId: integration.id,
|
|
192
|
+
uuid: event.uuid,
|
|
193
|
+
ownerWxid: event.ownerWxid,
|
|
194
|
+
contactId: event.contactId,
|
|
195
|
+
chatType: event.chatType,
|
|
196
|
+
senderId: event.senderId,
|
|
197
|
+
wechatChannel: this.wechatChannel
|
|
198
|
+
}, {
|
|
199
|
+
messageId: event.messageId,
|
|
200
|
+
status: 'thinking',
|
|
201
|
+
language: integration.options?.preferLanguage
|
|
202
|
+
});
|
|
203
|
+
const newSessionCommand = this.parseNewSessionCommand(dispatchable.input);
|
|
204
|
+
if (newSessionCommand.matched && conversationUserKey) {
|
|
205
|
+
await this.clearConversation(conversationUserKey, binding.xpertId, eventScope);
|
|
206
|
+
if (!newSessionCommand.input) {
|
|
207
|
+
await wechatMessage.reply(this.getNewConversationStartedText(integration.options?.preferLanguage));
|
|
208
|
+
await this.updateLog(inboundLog.id, {
|
|
209
|
+
status: 'dispatched',
|
|
210
|
+
xpertId: binding.xpertId,
|
|
211
|
+
conversationUserKey
|
|
212
|
+
}, eventScope);
|
|
213
|
+
return { handled: true, reason: 'new_session_only' };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
let conversationId;
|
|
217
|
+
if (conversationUserKey && !newSessionCommand.matched) {
|
|
218
|
+
const currentConversation = await this.getConversationState(conversationUserKey, binding.xpertId, eventScope);
|
|
219
|
+
const sessionTimeoutMs = this.resolveSessionTimeoutMs(binding.sessionTimeoutSeconds);
|
|
220
|
+
if (currentConversation?.conversationId) {
|
|
221
|
+
if (this.isConversationExpired(currentConversation.lastActiveAt, sessionTimeoutMs)) {
|
|
222
|
+
await this.clearConversation(conversationUserKey, binding.xpertId, eventScope);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
conversationId = currentConversation.conversationId;
|
|
226
|
+
await this.touchConversation(conversationUserKey, binding.xpertId, new Date(), eventScope);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const handled = await this.triggerStrategy.handleInboundMessage({
|
|
231
|
+
integrationId: integration.id,
|
|
232
|
+
input: newSessionCommand.matched ? newSessionCommand.input : dispatchable.input,
|
|
233
|
+
wechatMessage,
|
|
234
|
+
conversationId,
|
|
235
|
+
conversationUserKey,
|
|
236
|
+
tenantId: integration.tenantId || ctx.tenantId,
|
|
237
|
+
organizationId: integration.organizationId || ctx.organizationId,
|
|
238
|
+
executorUserId,
|
|
239
|
+
endUserId: event.senderId
|
|
240
|
+
});
|
|
241
|
+
await this.updateLog(inboundLog.id, {
|
|
242
|
+
status: handled ? 'dispatched' : 'failed',
|
|
243
|
+
xpertId: binding.xpertId,
|
|
244
|
+
conversationId,
|
|
245
|
+
conversationUserKey,
|
|
246
|
+
error: handled ? undefined : 'handoff_dispatch_failed'
|
|
247
|
+
}, eventScope);
|
|
248
|
+
return { handled, reason: handled ? 'dispatched' : 'dispatch_failed' };
|
|
249
|
+
}
|
|
250
|
+
async logOutbound(params) {
|
|
251
|
+
const context = params.context;
|
|
252
|
+
const bindingContext = this.resolveBindingContext();
|
|
253
|
+
const scope = this.resolveTenantScope(context, bindingContext);
|
|
254
|
+
await this.messageLogRepository.save({
|
|
255
|
+
integrationId: context.integrationId,
|
|
256
|
+
uuid: context.uuid,
|
|
257
|
+
ownerWxid: context.ownerWxid,
|
|
258
|
+
contactId: context.contactId,
|
|
259
|
+
senderId: context.senderId,
|
|
260
|
+
chatType: context.chatType,
|
|
261
|
+
messageId: params.messageId,
|
|
262
|
+
direction: 'outbound',
|
|
263
|
+
status: params.status,
|
|
264
|
+
content: params.content,
|
|
265
|
+
error: params.error,
|
|
266
|
+
xpertId: context.xpertId,
|
|
267
|
+
conversationId: context.conversationId,
|
|
268
|
+
conversationUserKey: context.conversationUserKey,
|
|
269
|
+
tenantId: scope.tenantId ?? null,
|
|
270
|
+
organizationId: scope.organizationId ?? null,
|
|
271
|
+
createdById: bindingContext.createdById ?? null,
|
|
272
|
+
updatedById: bindingContext.updatedById ?? null
|
|
273
|
+
});
|
|
274
|
+
await this.accountRepository.update(this.scopedWhere({
|
|
275
|
+
integrationId: context.integrationId,
|
|
276
|
+
uuid: context.uuid
|
|
277
|
+
}, scope), {
|
|
278
|
+
lastSendAt: new Date(),
|
|
279
|
+
status: params.status === 'failed' ? 'error' : 'online',
|
|
280
|
+
lastError: params.error ?? null
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
async setAccountEnabled(integrationId, uuid, enabled) {
|
|
284
|
+
const normalizedIntegrationId = normalizeConversationKey(integrationId);
|
|
285
|
+
const normalizedUuid = normalizeConversationKey(uuid);
|
|
286
|
+
if (!normalizedIntegrationId || !normalizedUuid) {
|
|
287
|
+
throw new Error('缺少个人微信账号标识。');
|
|
288
|
+
}
|
|
289
|
+
const scope = await this.readIntegrationTenantScope(normalizedIntegrationId);
|
|
290
|
+
await this.accountRepository.update(this.scopedWhere({
|
|
291
|
+
integrationId: normalizedIntegrationId,
|
|
292
|
+
uuid: normalizedUuid
|
|
293
|
+
}, scope), {
|
|
294
|
+
enabled,
|
|
295
|
+
status: enabled ? 'unknown' : 'disabled',
|
|
296
|
+
lastError: null
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
async resendOutboundMessage(integrationId, logId) {
|
|
300
|
+
const normalizedIntegrationId = normalizeConversationKey(integrationId);
|
|
301
|
+
if (!normalizedIntegrationId) {
|
|
302
|
+
throw new Error('缺少个人微信集成标识。');
|
|
303
|
+
}
|
|
304
|
+
const scope = await this.readIntegrationTenantScope(normalizedIntegrationId);
|
|
305
|
+
const target = logId
|
|
306
|
+
? await this.messageLogRepository.findOne({
|
|
307
|
+
where: this.scopedWhere({
|
|
308
|
+
id: normalizeConversationKey(logId),
|
|
309
|
+
integrationId: normalizedIntegrationId,
|
|
310
|
+
direction: 'outbound'
|
|
311
|
+
}, scope)
|
|
312
|
+
})
|
|
313
|
+
: await this.messageLogRepository.findOne({
|
|
314
|
+
where: this.scopedWhere({
|
|
315
|
+
integrationId: normalizedIntegrationId,
|
|
316
|
+
direction: 'outbound'
|
|
317
|
+
}, scope),
|
|
318
|
+
order: {
|
|
319
|
+
createdAt: 'DESC'
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
if (!target?.uuid || !target.contactId || !target.content) {
|
|
323
|
+
throw new Error('没有可重发的 AI 文本回复。');
|
|
324
|
+
}
|
|
325
|
+
const result = await this.wechatChannel.sendTextByIntegrationId(normalizedIntegrationId, {
|
|
326
|
+
uuid: target.uuid,
|
|
327
|
+
contactId: target.contactId,
|
|
328
|
+
content: target.content
|
|
329
|
+
});
|
|
330
|
+
await this.messageLogRepository.save({
|
|
331
|
+
integrationId: normalizedIntegrationId,
|
|
332
|
+
uuid: target.uuid,
|
|
333
|
+
ownerWxid: target.ownerWxid,
|
|
334
|
+
contactId: target.contactId,
|
|
335
|
+
senderId: target.senderId,
|
|
336
|
+
messageId: result.messageId,
|
|
337
|
+
chatType: target.chatType,
|
|
338
|
+
direction: 'outbound',
|
|
339
|
+
status: result.success ? 'sent' : 'failed',
|
|
340
|
+
content: target.content,
|
|
341
|
+
error: result.error,
|
|
342
|
+
xpertId: target.xpertId,
|
|
343
|
+
conversationId: target.conversationId,
|
|
344
|
+
conversationUserKey: target.conversationUserKey,
|
|
345
|
+
tenantId: target.tenantId ?? scope.tenantId ?? null,
|
|
346
|
+
organizationId: target.organizationId ?? scope.organizationId ?? null
|
|
347
|
+
});
|
|
348
|
+
return result;
|
|
349
|
+
}
|
|
350
|
+
async getWorkbenchData(integrationId, query = {}) {
|
|
351
|
+
const integration = await this.integrationPermissionService.read(integrationId, {
|
|
352
|
+
relations: ['tenant']
|
|
353
|
+
});
|
|
354
|
+
const normalizedIntegrationId = normalizeConversationKey(integrationId);
|
|
355
|
+
if (!integration || !normalizedIntegrationId) {
|
|
356
|
+
throw new Error('个人微信集成不存在或无权访问。');
|
|
357
|
+
}
|
|
358
|
+
const pageSize = this.normalizePositiveInt(query.pageSize) ?? 30;
|
|
359
|
+
const search = this.normalizeListSearch(query.search);
|
|
360
|
+
const scope = this.resolveTenantScope(integration);
|
|
361
|
+
const [accounts, logs, bindings] = await Promise.all([
|
|
362
|
+
this.accountRepository.find({
|
|
363
|
+
where: this.scopedWhere({ integrationId: normalizedIntegrationId }, scope),
|
|
364
|
+
order: { updatedAt: 'DESC' },
|
|
365
|
+
take: 100
|
|
366
|
+
}),
|
|
367
|
+
this.messageLogRepository.find({
|
|
368
|
+
where: this.scopedWhere({ integrationId: normalizedIntegrationId }, scope),
|
|
369
|
+
order: { createdAt: 'DESC' },
|
|
370
|
+
take: 200
|
|
371
|
+
}),
|
|
372
|
+
this.conversationBindingRepository.find({
|
|
373
|
+
where: this.scopedWhere({}, scope),
|
|
374
|
+
order: { updatedAt: 'DESC' }
|
|
375
|
+
})
|
|
376
|
+
]);
|
|
377
|
+
const conversations = bindings
|
|
378
|
+
.map((binding) => this.toConversationListItem(binding, normalizedIntegrationId))
|
|
379
|
+
.filter((item) => Boolean(item))
|
|
380
|
+
.filter((item) => {
|
|
381
|
+
if (!search) {
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
return [item.uuid, item.contactId, item.senderId, item.xpertId, item.conversationId].some((value) => this.normalizeListSearch(value)?.includes(search));
|
|
385
|
+
})
|
|
386
|
+
.slice(0, pageSize);
|
|
387
|
+
const filteredLogs = logs
|
|
388
|
+
.filter((log) => {
|
|
389
|
+
if (!search) {
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
392
|
+
return [log.uuid, log.contactId, log.senderId, log.content, log.error, log.status].some((value) => this.normalizeListSearch(value)?.includes(search));
|
|
393
|
+
})
|
|
394
|
+
.slice(0, pageSize);
|
|
395
|
+
return {
|
|
396
|
+
scope: 'integration',
|
|
397
|
+
integrationId: normalizedIntegrationId,
|
|
398
|
+
integrations: [
|
|
399
|
+
this.toIntegrationWorkbenchItem(integration, {
|
|
400
|
+
accounts,
|
|
401
|
+
conversations,
|
|
402
|
+
logs
|
|
403
|
+
})
|
|
404
|
+
],
|
|
405
|
+
callbackConfig: this.buildCallbackConfig(integrationId, integration?.options?.callbackSecret),
|
|
406
|
+
summary: {
|
|
407
|
+
integrationCount: 1,
|
|
408
|
+
accountCount: accounts.length,
|
|
409
|
+
conversationCount: conversations.length,
|
|
410
|
+
recentMessageCount: logs.length,
|
|
411
|
+
errorCount: logs.filter((log) => log.status === 'failed' || log.error).length
|
|
412
|
+
},
|
|
413
|
+
accounts,
|
|
414
|
+
conversations,
|
|
415
|
+
messages: filteredLogs,
|
|
416
|
+
logs: filteredLogs,
|
|
417
|
+
config: {
|
|
418
|
+
baseUrl: integration?.options?.baseUrl,
|
|
419
|
+
apiVersion: integration?.options?.apiVersion ?? '/v1/',
|
|
420
|
+
timeoutMs: integration?.options?.timeoutMs ?? 10000,
|
|
421
|
+
preferLanguage: integration?.options?.preferLanguage,
|
|
422
|
+
groupTriggerMode: integration?.options?.groupTriggerMode ?? 'mention_or_keywords',
|
|
423
|
+
groupKeywords: integration?.options?.groupKeywords ?? [],
|
|
424
|
+
ignoreSelfMessages: integration?.options?.ignoreSelfMessages ?? true,
|
|
425
|
+
fallbackToLegacySendText: integration?.options?.fallbackToLegacySendText !== false,
|
|
426
|
+
callbackSecret: integration?.options?.callbackSecret ? '******' : ''
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
async getOrganizationWorkbenchData(query = {}) {
|
|
431
|
+
const integrations = await this.listWechatPersonalIntegrations();
|
|
432
|
+
const integrationIds = integrations.map((integration) => normalizeConversationKey(integration.id)).filter(Boolean);
|
|
433
|
+
const pageSize = this.normalizePositiveInt(query.pageSize) ?? 30;
|
|
434
|
+
const search = this.normalizeListSearch(query.search);
|
|
435
|
+
const scope = this.resolveTenantScope(integrations[0]);
|
|
436
|
+
if (!integrationIds.length) {
|
|
437
|
+
return {
|
|
438
|
+
scope: 'organization',
|
|
439
|
+
integrationId: null,
|
|
440
|
+
integrations: [],
|
|
441
|
+
callbackConfig: this.emptyCallbackConfig(),
|
|
442
|
+
summary: {
|
|
443
|
+
integrationCount: 0,
|
|
444
|
+
accountCount: 0,
|
|
445
|
+
conversationCount: 0,
|
|
446
|
+
recentMessageCount: 0,
|
|
447
|
+
errorCount: 0
|
|
448
|
+
},
|
|
449
|
+
accounts: [],
|
|
450
|
+
conversations: [],
|
|
451
|
+
messages: [],
|
|
452
|
+
logs: [],
|
|
453
|
+
config: {
|
|
454
|
+
organizationScope: true,
|
|
455
|
+
integrationCount: 0
|
|
456
|
+
}
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
const [accounts, logs, bindings] = await Promise.all([
|
|
460
|
+
this.accountRepository.find({
|
|
461
|
+
where: integrationIds.map((integrationId) => this.scopedWhere({ integrationId }, scope)),
|
|
462
|
+
order: { updatedAt: 'DESC' },
|
|
463
|
+
take: 500
|
|
464
|
+
}),
|
|
465
|
+
this.messageLogRepository.find({
|
|
466
|
+
where: integrationIds.map((integrationId) => this.scopedWhere({ integrationId }, scope)),
|
|
467
|
+
order: { createdAt: 'DESC' },
|
|
468
|
+
take: 1000
|
|
469
|
+
}),
|
|
470
|
+
this.conversationBindingRepository.find({
|
|
471
|
+
where: this.scopedWhere({}, scope),
|
|
472
|
+
order: { updatedAt: 'DESC' },
|
|
473
|
+
take: 1500
|
|
474
|
+
})
|
|
475
|
+
]);
|
|
476
|
+
const integrationIdSet = new Set(integrationIds);
|
|
477
|
+
const allConversations = bindings
|
|
478
|
+
.map((binding) => {
|
|
479
|
+
const parsed = parseWechatPersonalConversationUserKey(binding.conversationUserKey);
|
|
480
|
+
if (!parsed || !integrationIdSet.has(parsed.integrationId)) {
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
return this.toConversationListItem(binding, parsed.integrationId);
|
|
484
|
+
})
|
|
485
|
+
.filter((item) => Boolean(item));
|
|
486
|
+
const conversations = allConversations
|
|
487
|
+
.filter((item) => this.matchesConversationSearch(item, search))
|
|
488
|
+
.slice(0, pageSize);
|
|
489
|
+
const filteredLogs = logs
|
|
490
|
+
.filter((log) => this.matchesLogSearch(log, search))
|
|
491
|
+
.slice(0, pageSize);
|
|
492
|
+
return {
|
|
493
|
+
scope: 'organization',
|
|
494
|
+
integrationId: null,
|
|
495
|
+
integrations: integrations.map((integration) => this.toIntegrationWorkbenchItem(integration, {
|
|
496
|
+
accounts: accounts.filter((account) => account.integrationId === integration.id),
|
|
497
|
+
conversations: allConversations.filter((conversation) => conversation.integrationId === integration.id),
|
|
498
|
+
logs: logs.filter((log) => log.integrationId === integration.id)
|
|
499
|
+
})),
|
|
500
|
+
callbackConfig: this.emptyCallbackConfig(),
|
|
501
|
+
summary: {
|
|
502
|
+
integrationCount: integrations.length,
|
|
503
|
+
accountCount: accounts.length,
|
|
504
|
+
conversationCount: allConversations.length,
|
|
505
|
+
recentMessageCount: logs.length,
|
|
506
|
+
errorCount: logs.filter((log) => log.status === 'failed' || log.error).length
|
|
507
|
+
},
|
|
508
|
+
accounts,
|
|
509
|
+
conversations,
|
|
510
|
+
messages: filteredLogs,
|
|
511
|
+
logs: filteredLogs,
|
|
512
|
+
config: {
|
|
513
|
+
organizationScope: true,
|
|
514
|
+
integrationCount: integrations.length
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
async getRuntimeStatus(integrationId) {
|
|
519
|
+
const normalizedIntegrationId = normalizeConversationKey(integrationId);
|
|
520
|
+
if (!normalizedIntegrationId) {
|
|
521
|
+
throw new Error('缺少个人微信集成标识。');
|
|
522
|
+
}
|
|
523
|
+
const [workbenchData, triggerBinding] = await Promise.all([
|
|
524
|
+
this.getWorkbenchData(normalizedIntegrationId, { pageSize: 20 }),
|
|
525
|
+
this.triggerStrategy.getBinding(normalizedIntegrationId)
|
|
526
|
+
]);
|
|
527
|
+
return {
|
|
528
|
+
callbackConfig: workbenchData.callbackConfig,
|
|
529
|
+
summary: workbenchData.summary,
|
|
530
|
+
triggerBinding: triggerBinding
|
|
531
|
+
? {
|
|
532
|
+
integrationId: triggerBinding.integrationId,
|
|
533
|
+
xpertId: triggerBinding.xpertId,
|
|
534
|
+
sessionTimeoutSeconds: triggerBinding.sessionTimeoutSeconds,
|
|
535
|
+
summaryWindowSeconds: triggerBinding.summaryWindowSeconds,
|
|
536
|
+
groupTriggerMode: triggerBinding.groupTriggerMode,
|
|
537
|
+
groupKeywords: triggerBinding.groupKeywords ?? [],
|
|
538
|
+
updatedAt: this.normalizeDate(triggerBinding.updatedAt) ?? null
|
|
539
|
+
}
|
|
540
|
+
: null,
|
|
541
|
+
accounts: workbenchData.accounts.slice(0, 10),
|
|
542
|
+
recentErrors: workbenchData.logs
|
|
543
|
+
.filter((log) => log.status === 'failed' || Boolean(log.error))
|
|
544
|
+
.slice(0, 10),
|
|
545
|
+
config: workbenchData.config
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
async getOrganizationRuntimeStatus() {
|
|
549
|
+
const workbenchData = await this.getOrganizationWorkbenchData({ pageSize: 20 });
|
|
550
|
+
return {
|
|
551
|
+
scope: 'organization',
|
|
552
|
+
integrations: workbenchData.integrations,
|
|
553
|
+
callbackConfig: workbenchData.callbackConfig,
|
|
554
|
+
summary: workbenchData.summary,
|
|
555
|
+
triggerBinding: null,
|
|
556
|
+
accounts: workbenchData.accounts.slice(0, 10),
|
|
557
|
+
recentErrors: workbenchData.logs
|
|
558
|
+
.filter((log) => log.status === 'failed' || Boolean(log.error))
|
|
559
|
+
.slice(0, 10),
|
|
560
|
+
config: workbenchData.config
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
async getBoundIntegrationIdForXpert(xpertId) {
|
|
564
|
+
const normalizedXpertId = normalizeConversationKey(xpertId);
|
|
565
|
+
if (!normalizedXpertId) {
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
return this.triggerStrategy.getBoundIntegrationId(normalizedXpertId);
|
|
569
|
+
}
|
|
570
|
+
async getWorkbenchTableData(integrationId, table, query = {}) {
|
|
571
|
+
if (table === 'accounts') {
|
|
572
|
+
return { key: table, ...(await this.listAccounts(integrationId, query)) };
|
|
573
|
+
}
|
|
574
|
+
if (table === 'conversations') {
|
|
575
|
+
return { key: table, ...(await this.listConversations(integrationId, query)) };
|
|
576
|
+
}
|
|
577
|
+
return { key: table, ...(await this.searchMessageLogs(integrationId, query)) };
|
|
578
|
+
}
|
|
579
|
+
async getOrganizationWorkbenchTableData(table, query = {}) {
|
|
580
|
+
if (table === 'accounts') {
|
|
581
|
+
return { key: table, ...(await this.listOrganizationAccounts(query)) };
|
|
582
|
+
}
|
|
583
|
+
if (table === 'conversations') {
|
|
584
|
+
return { key: table, ...(await this.listOrganizationConversations(query)) };
|
|
585
|
+
}
|
|
586
|
+
return { key: table, ...(await this.searchOrganizationMessageLogs(query)) };
|
|
587
|
+
}
|
|
588
|
+
async listAccounts(integrationId, query = {}) {
|
|
589
|
+
const normalizedIntegrationId = normalizeConversationKey(integrationId);
|
|
590
|
+
if (!normalizedIntegrationId) {
|
|
591
|
+
throw new Error('缺少个人微信集成标识。');
|
|
592
|
+
}
|
|
593
|
+
const page = this.normalizePage(query.page);
|
|
594
|
+
const pageSize = this.normalizePageSize(query.pageSize, 50);
|
|
595
|
+
const search = this.normalizeListSearch(query.search);
|
|
596
|
+
const filters = this.normalizeFilters(query.filters);
|
|
597
|
+
const scope = await this.readIntegrationTenantScope(normalizedIntegrationId);
|
|
598
|
+
const accounts = await this.accountRepository.find({
|
|
599
|
+
where: this.scopedWhere({ integrationId: normalizedIntegrationId }, scope),
|
|
600
|
+
order: { updatedAt: 'DESC' },
|
|
601
|
+
take: 500
|
|
602
|
+
});
|
|
603
|
+
const filtered = accounts.filter((account) => {
|
|
604
|
+
if (!this.matchesAccountFilters(account, filters)) {
|
|
605
|
+
return false;
|
|
606
|
+
}
|
|
607
|
+
if (!search) {
|
|
608
|
+
return true;
|
|
609
|
+
}
|
|
610
|
+
return [
|
|
611
|
+
account.uuid,
|
|
612
|
+
account.ownerWxid,
|
|
613
|
+
account.displayName,
|
|
614
|
+
account.status,
|
|
615
|
+
account.lastError
|
|
616
|
+
].some((value) => this.normalizeListSearch(value)?.includes(search));
|
|
617
|
+
});
|
|
618
|
+
return this.paginateItems(filtered, page, pageSize);
|
|
619
|
+
}
|
|
620
|
+
async listOrganizationAccounts(query = {}) {
|
|
621
|
+
const data = await this.getOrganizationWorkbenchData({ pageSize: 500 });
|
|
622
|
+
const page = this.normalizePage(query.page);
|
|
623
|
+
const pageSize = this.normalizePageSize(query.pageSize, 50);
|
|
624
|
+
const search = this.normalizeListSearch(query.search);
|
|
625
|
+
const filters = this.normalizeFilters(query.filters);
|
|
626
|
+
const filtered = data.accounts.filter((account) => {
|
|
627
|
+
if (!this.matchesAccountFilters(account, filters)) {
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
630
|
+
if (!search) {
|
|
631
|
+
return true;
|
|
632
|
+
}
|
|
633
|
+
return [
|
|
634
|
+
account.integrationId,
|
|
635
|
+
account.uuid,
|
|
636
|
+
account.ownerWxid,
|
|
637
|
+
account.displayName,
|
|
638
|
+
account.status,
|
|
639
|
+
account.lastError
|
|
640
|
+
].some((value) => this.normalizeListSearch(value)?.includes(search));
|
|
641
|
+
});
|
|
642
|
+
return this.paginateItems(filtered, page, pageSize);
|
|
643
|
+
}
|
|
644
|
+
async listConversations(integrationId, query = {}) {
|
|
645
|
+
const normalizedIntegrationId = normalizeConversationKey(integrationId);
|
|
646
|
+
if (!normalizedIntegrationId) {
|
|
647
|
+
throw new Error('缺少个人微信集成标识。');
|
|
648
|
+
}
|
|
649
|
+
const page = this.normalizePage(query.page);
|
|
650
|
+
const pageSize = this.normalizePageSize(query.pageSize, 50);
|
|
651
|
+
const search = this.normalizeListSearch(query.search);
|
|
652
|
+
const filters = this.normalizeFilters(query.filters);
|
|
653
|
+
const scope = await this.readIntegrationTenantScope(normalizedIntegrationId);
|
|
654
|
+
const bindings = await this.conversationBindingRepository.find({
|
|
655
|
+
where: this.scopedWhere({}, scope),
|
|
656
|
+
order: { updatedAt: 'DESC' },
|
|
657
|
+
take: 1000
|
|
658
|
+
});
|
|
659
|
+
const conversations = bindings
|
|
660
|
+
.map((binding) => this.toConversationListItem(binding, normalizedIntegrationId))
|
|
661
|
+
.filter((item) => Boolean(item))
|
|
662
|
+
.filter((item) => this.matchesConversationFilters(item, filters))
|
|
663
|
+
.filter((item) => {
|
|
664
|
+
if (!search) {
|
|
665
|
+
return true;
|
|
666
|
+
}
|
|
667
|
+
return [
|
|
668
|
+
item.id,
|
|
669
|
+
item.uuid,
|
|
670
|
+
item.contactId,
|
|
671
|
+
item.senderId,
|
|
672
|
+
item.xpertId,
|
|
673
|
+
item.conversationId
|
|
674
|
+
].some((value) => this.normalizeListSearch(value)?.includes(search));
|
|
675
|
+
});
|
|
676
|
+
return this.paginateItems(conversations, page, pageSize);
|
|
677
|
+
}
|
|
678
|
+
async listOrganizationConversations(query = {}) {
|
|
679
|
+
const data = await this.getOrganizationWorkbenchData({ pageSize: 1000 });
|
|
680
|
+
const page = this.normalizePage(query.page);
|
|
681
|
+
const pageSize = this.normalizePageSize(query.pageSize, 50);
|
|
682
|
+
const search = this.normalizeListSearch(query.search);
|
|
683
|
+
const filters = this.normalizeFilters(query.filters);
|
|
684
|
+
const filtered = data.conversations
|
|
685
|
+
.filter((item) => this.matchesConversationFilters(item, filters))
|
|
686
|
+
.filter((item) => this.matchesConversationSearch(item, search));
|
|
687
|
+
return this.paginateItems(filtered, page, pageSize);
|
|
688
|
+
}
|
|
689
|
+
async searchMessageLogs(integrationId, query = {}) {
|
|
690
|
+
const normalizedIntegrationId = normalizeConversationKey(integrationId);
|
|
691
|
+
if (!normalizedIntegrationId) {
|
|
692
|
+
throw new Error('缺少个人微信集成标识。');
|
|
693
|
+
}
|
|
694
|
+
const page = this.normalizePage(query.page);
|
|
695
|
+
const pageSize = this.normalizePageSize(query.pageSize, 50);
|
|
696
|
+
const search = this.normalizeListSearch(query.search);
|
|
697
|
+
const filters = this.normalizeFilters(query.filters);
|
|
698
|
+
const direction = this.normalizeDirection(query.direction ?? filters.direction);
|
|
699
|
+
const status = this.normalizeLogStatus(query.status ?? filters.status);
|
|
700
|
+
const scope = await this.readIntegrationTenantScope(normalizedIntegrationId);
|
|
701
|
+
const logs = await this.messageLogRepository.find({
|
|
702
|
+
where: this.scopedWhere({ integrationId: normalizedIntegrationId }, scope),
|
|
703
|
+
order: { createdAt: 'DESC' },
|
|
704
|
+
take: 1000
|
|
705
|
+
});
|
|
706
|
+
const filtered = logs.filter((log) => {
|
|
707
|
+
if (!this.matchesLogFilters(log, filters)) {
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
if (direction && log.direction !== direction) {
|
|
711
|
+
return false;
|
|
712
|
+
}
|
|
713
|
+
if (status && log.status !== status) {
|
|
714
|
+
return false;
|
|
715
|
+
}
|
|
716
|
+
if (!search) {
|
|
717
|
+
return true;
|
|
718
|
+
}
|
|
719
|
+
return [
|
|
720
|
+
log.uuid,
|
|
721
|
+
log.ownerWxid,
|
|
722
|
+
log.contactId,
|
|
723
|
+
log.senderId,
|
|
724
|
+
log.messageId,
|
|
725
|
+
log.content,
|
|
726
|
+
log.error,
|
|
727
|
+
log.xpertId,
|
|
728
|
+
log.conversationId,
|
|
729
|
+
log.conversationUserKey
|
|
730
|
+
].some((value) => this.normalizeListSearch(value)?.includes(search));
|
|
731
|
+
});
|
|
732
|
+
return this.paginateItems(filtered, page, pageSize);
|
|
733
|
+
}
|
|
734
|
+
async searchOrganizationMessageLogs(query = {}) {
|
|
735
|
+
const data = await this.getOrganizationWorkbenchData({ pageSize: 1000 });
|
|
736
|
+
const page = this.normalizePage(query.page);
|
|
737
|
+
const pageSize = this.normalizePageSize(query.pageSize, 50);
|
|
738
|
+
const search = this.normalizeListSearch(query.search);
|
|
739
|
+
const filters = this.normalizeFilters(query.filters);
|
|
740
|
+
const direction = this.normalizeDirection(query.direction ?? filters.direction);
|
|
741
|
+
const status = this.normalizeLogStatus(query.status ?? filters.status);
|
|
742
|
+
const filtered = data.logs.filter((log) => {
|
|
743
|
+
if (!this.matchesLogFilters(log, filters)) {
|
|
744
|
+
return false;
|
|
745
|
+
}
|
|
746
|
+
if (direction && log.direction !== direction) {
|
|
747
|
+
return false;
|
|
748
|
+
}
|
|
749
|
+
if (status && log.status !== status) {
|
|
750
|
+
return false;
|
|
751
|
+
}
|
|
752
|
+
return this.matchesLogSearch(log, search);
|
|
753
|
+
});
|
|
754
|
+
return this.paginateItems(filtered, page, pageSize);
|
|
755
|
+
}
|
|
756
|
+
buildCallbackConfig(integrationId, callbackSecret) {
|
|
757
|
+
const apiBaseUrl = (process.env.API_BASE_URL || '').replace(/\/+$/, '');
|
|
758
|
+
const id = normalizeConversationKey(integrationId) || '<integrationId>';
|
|
759
|
+
const webhookUrl = `${apiBaseUrl}/api/wechat-personal/webhook/${id}${callbackSecret ? `?secret=${encodeURIComponent(callbackSecret)}` : ''}`;
|
|
760
|
+
const setCallbackUrlTemplate = `${apiBaseUrl}/api/wechat-personal/webhook/${id}${callbackSecret ? '?secret=***' : ''}`;
|
|
761
|
+
return {
|
|
762
|
+
webhookUrl,
|
|
763
|
+
globalWebhookUrl: webhookUrl,
|
|
764
|
+
setCallbackUrlTemplate,
|
|
765
|
+
setCallbackCurlTemplate: `curl -X POST "$WX2_BASE_URL/message/SetCallback?key=<uuid>" ` +
|
|
766
|
+
`-H "Content-Type: application/json" ` +
|
|
767
|
+
`-d '{"CallbackURL":"${setCallbackUrlTemplate}","Enabled":true}'`
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
async upsertAccount(integration, event, ctx) {
|
|
771
|
+
const bindingContext = this.resolveBindingContext();
|
|
772
|
+
const scope = this.resolveTenantScope(integration, ctx);
|
|
773
|
+
const existing = await this.accountRepository.findOne({
|
|
774
|
+
where: this.scopedWhere({
|
|
775
|
+
integrationId: integration.id,
|
|
776
|
+
uuid: event.uuid
|
|
777
|
+
}, scope)
|
|
778
|
+
});
|
|
779
|
+
const enabled = existing?.enabled !== false;
|
|
780
|
+
await this.accountRepository.upsert({
|
|
781
|
+
integrationId: integration.id,
|
|
782
|
+
uuid: event.uuid,
|
|
783
|
+
ownerWxid: event.ownerWxid || null,
|
|
784
|
+
displayName: event.ownerName || event.ownerWxid || null,
|
|
785
|
+
status: enabled ? 'online' : 'disabled',
|
|
786
|
+
enabled,
|
|
787
|
+
lastCallbackAt: new Date(),
|
|
788
|
+
lastError: null,
|
|
789
|
+
tenantId: scope.tenantId ?? null,
|
|
790
|
+
organizationId: scope.organizationId ?? null,
|
|
791
|
+
createdById: bindingContext.createdById ?? null,
|
|
792
|
+
updatedById: bindingContext.updatedById ?? null
|
|
793
|
+
}, ['integrationId', 'uuid']);
|
|
794
|
+
return { enabled };
|
|
795
|
+
}
|
|
796
|
+
async isDuplicateInbound(integrationId, event, scope) {
|
|
797
|
+
const messageId = event.messageId;
|
|
798
|
+
if (!normalizeConversationKey(messageId)) {
|
|
799
|
+
return false;
|
|
800
|
+
}
|
|
801
|
+
const exactCount = await this.messageLogRepository.count({
|
|
802
|
+
where: this.scopedWhere({
|
|
803
|
+
integrationId,
|
|
804
|
+
messageId,
|
|
805
|
+
direction: 'inbound'
|
|
806
|
+
}, scope)
|
|
807
|
+
});
|
|
808
|
+
if (exactCount > 0) {
|
|
809
|
+
return true;
|
|
810
|
+
}
|
|
811
|
+
const content = normalizeConversationKey(event.content);
|
|
812
|
+
if (!content) {
|
|
813
|
+
return false;
|
|
814
|
+
}
|
|
815
|
+
const recentEquivalentCount = await this.messageLogRepository.count({
|
|
816
|
+
where: this.scopedWhere({
|
|
817
|
+
integrationId,
|
|
818
|
+
uuid: event.uuid,
|
|
819
|
+
contactId: event.contactId,
|
|
820
|
+
senderId: event.senderId,
|
|
821
|
+
direction: 'inbound',
|
|
822
|
+
content,
|
|
823
|
+
createdAt: MoreThan(new Date(Date.now() - 5_000))
|
|
824
|
+
}, scope)
|
|
825
|
+
});
|
|
826
|
+
return recentEquivalentCount > 0;
|
|
827
|
+
}
|
|
828
|
+
async logInbound(integration, event, status, params = {}) {
|
|
829
|
+
const bindingContext = this.resolveBindingContext();
|
|
830
|
+
const scope = this.resolveTenantScope(integration, bindingContext);
|
|
831
|
+
return this.messageLogRepository.save({
|
|
832
|
+
integrationId: integration.id,
|
|
833
|
+
uuid: event.uuid,
|
|
834
|
+
ownerWxid: event.ownerWxid,
|
|
835
|
+
contactId: event.contactId,
|
|
836
|
+
senderId: event.senderId,
|
|
837
|
+
messageId: event.messageId,
|
|
838
|
+
chatType: event.chatType,
|
|
839
|
+
direction: 'inbound',
|
|
840
|
+
status,
|
|
841
|
+
content: event.content,
|
|
842
|
+
payloadSummary: summarizePayload(event.rawPayload),
|
|
843
|
+
error: params.error,
|
|
844
|
+
tenantId: scope.tenantId ?? null,
|
|
845
|
+
organizationId: scope.organizationId ?? null,
|
|
846
|
+
createdById: bindingContext.createdById ?? null,
|
|
847
|
+
updatedById: bindingContext.updatedById ?? null
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
async updateLog(id, patch, scope) {
|
|
851
|
+
if (!id) {
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
await this.messageLogRepository.update(this.scopedWhere({ id }, scope), patch);
|
|
855
|
+
}
|
|
856
|
+
getConversationCacheKey(conversationUserKey, xpertId, scope) {
|
|
857
|
+
const tenantKey = scope?.tenantId ? `tenant:${scope.tenantId}` : 'tenant:any';
|
|
858
|
+
const organizationKey = scope?.organizationId ? `org:${scope.organizationId}` : 'org:any';
|
|
859
|
+
return `wechat-personal:chat:${tenantKey}:${organizationKey}:${conversationUserKey}:${xpertId}`;
|
|
860
|
+
}
|
|
861
|
+
async cacheConversation(conversationUserKey, xpertId, conversationId, lastActiveAt, scope) {
|
|
862
|
+
await this.cacheManager.set(this.getConversationCacheKey(conversationUserKey, xpertId, scope), {
|
|
863
|
+
conversationId,
|
|
864
|
+
lastActiveAt: lastActiveAt?.toISOString()
|
|
865
|
+
}, CACHE_TTL_MS);
|
|
866
|
+
}
|
|
867
|
+
resolveTenantScope(primary, fallback) {
|
|
868
|
+
const bindingContext = this.resolveBindingContext();
|
|
869
|
+
return {
|
|
870
|
+
tenantId: primary?.tenantId ?? fallback?.tenantId ?? bindingContext.tenantId ?? null,
|
|
871
|
+
organizationId: primary?.organizationId ?? fallback?.organizationId ?? bindingContext.organizationId ?? null
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
async readIntegrationTenantScope(integrationId) {
|
|
875
|
+
const normalizedIntegrationId = normalizeConversationKey(integrationId);
|
|
876
|
+
if (!normalizedIntegrationId) {
|
|
877
|
+
throw new Error('缺少个人微信集成标识。');
|
|
878
|
+
}
|
|
879
|
+
const integration = await this.integrationPermissionService.read(normalizedIntegrationId, {
|
|
880
|
+
relations: ['tenant']
|
|
881
|
+
});
|
|
882
|
+
if (!integration) {
|
|
883
|
+
throw new Error('个人微信集成不存在或无权访问。');
|
|
884
|
+
}
|
|
885
|
+
return this.resolveTenantScope(integration);
|
|
886
|
+
}
|
|
887
|
+
scopedWhere(where, scope) {
|
|
888
|
+
const scoped = { ...where };
|
|
889
|
+
if (scope?.tenantId) {
|
|
890
|
+
scoped.tenantId = scope.tenantId;
|
|
891
|
+
}
|
|
892
|
+
if (scope?.organizationId) {
|
|
893
|
+
scoped.organizationId = scope.organizationId;
|
|
894
|
+
}
|
|
895
|
+
return scoped;
|
|
896
|
+
}
|
|
897
|
+
resolveBindingContext() {
|
|
898
|
+
const tenantId = RequestContext.currentTenantId();
|
|
899
|
+
const organizationId = RequestContext.getOrganizationId();
|
|
900
|
+
const userId = normalizeConversationKey(RequestContext.currentUserId());
|
|
901
|
+
const executionUserId = userId && UUID_PATTERN.test(userId) ? userId : null;
|
|
902
|
+
return {
|
|
903
|
+
tenantId: tenantId ?? null,
|
|
904
|
+
organizationId: organizationId ?? null,
|
|
905
|
+
createdById: executionUserId,
|
|
906
|
+
updatedById: executionUserId
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
resolveExecutionUserId(integration) {
|
|
910
|
+
const candidates = [RequestContext.currentUserId(), integration?.createdById, integration?.updatedById]
|
|
911
|
+
.map((value) => normalizeConversationKey(value))
|
|
912
|
+
.filter((value) => Boolean(value));
|
|
913
|
+
const uuidMatched = candidates.find((value) => UUID_PATTERN.test(value));
|
|
914
|
+
return uuidMatched ?? candidates[0];
|
|
915
|
+
}
|
|
916
|
+
async listWechatPersonalIntegrations() {
|
|
917
|
+
const result = await this.integrationPermissionService.findAll({
|
|
918
|
+
where: {
|
|
919
|
+
provider: WECHAT_PERSONAL_PROVIDER_KEY
|
|
920
|
+
},
|
|
921
|
+
relations: ['tenant'],
|
|
922
|
+
order: {
|
|
923
|
+
updatedAt: 'DESC'
|
|
924
|
+
},
|
|
925
|
+
take: 100
|
|
926
|
+
});
|
|
927
|
+
return (result.items ?? []).filter((integration) => integration?.provider === WECHAT_PERSONAL_PROVIDER_KEY);
|
|
928
|
+
}
|
|
929
|
+
toIntegrationWorkbenchItem(integration, stats) {
|
|
930
|
+
return {
|
|
931
|
+
id: integration.id,
|
|
932
|
+
name: integration.name,
|
|
933
|
+
description: integration.description,
|
|
934
|
+
slug: integration.slug,
|
|
935
|
+
callbackConfig: this.buildCallbackConfig(integration.id, integration.options?.callbackSecret),
|
|
936
|
+
accountCount: stats.accounts.length,
|
|
937
|
+
conversationCount: stats.conversations.length,
|
|
938
|
+
recentMessageCount: stats.logs.length,
|
|
939
|
+
errorCount: stats.logs.filter((log) => log.status === 'failed' || log.error).length,
|
|
940
|
+
config: this.sanitizeIntegrationConfig(integration.options)
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
sanitizeIntegrationConfig(options) {
|
|
944
|
+
return {
|
|
945
|
+
baseUrl: options?.baseUrl,
|
|
946
|
+
apiVersion: options?.apiVersion ?? '/v1/',
|
|
947
|
+
timeoutMs: options?.timeoutMs ?? 10000,
|
|
948
|
+
preferLanguage: options?.preferLanguage,
|
|
949
|
+
groupTriggerMode: options?.groupTriggerMode ?? 'mention_or_keywords',
|
|
950
|
+
groupKeywords: options?.groupKeywords ?? [],
|
|
951
|
+
ignoreSelfMessages: options?.ignoreSelfMessages ?? true,
|
|
952
|
+
fallbackToLegacySendText: options?.fallbackToLegacySendText !== false,
|
|
953
|
+
callbackSecret: options?.callbackSecret ? '******' : ''
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
emptyCallbackConfig() {
|
|
957
|
+
return {
|
|
958
|
+
webhookUrl: '',
|
|
959
|
+
globalWebhookUrl: '',
|
|
960
|
+
setCallbackUrlTemplate: '',
|
|
961
|
+
setCallbackCurlTemplate: ''
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
matchesAccountFilters(account, filters) {
|
|
965
|
+
const status = this.normalizeListSearch(filters.status);
|
|
966
|
+
const enabled = this.normalizeBooleanFilter(filters.enabled);
|
|
967
|
+
if (status && this.normalizeListSearch(account.status) !== status) {
|
|
968
|
+
return false;
|
|
969
|
+
}
|
|
970
|
+
if (enabled !== null && account.enabled !== enabled) {
|
|
971
|
+
return false;
|
|
972
|
+
}
|
|
973
|
+
return (this.matchesFilter(account.integrationId, filters.integrationId) &&
|
|
974
|
+
this.matchesFilter(account.uuid, filters.uuid) &&
|
|
975
|
+
this.matchesFilter(account.ownerWxid, filters.ownerWxid));
|
|
976
|
+
}
|
|
977
|
+
matchesConversationFilters(item, filters) {
|
|
978
|
+
const chatType = this.normalizeChatType(filters.chatType);
|
|
979
|
+
if (chatType && item.chatType !== chatType) {
|
|
980
|
+
return false;
|
|
981
|
+
}
|
|
982
|
+
return (this.matchesFilter(item.integrationId, filters.integrationId) &&
|
|
983
|
+
this.matchesFilter(item.uuid, filters.uuid) &&
|
|
984
|
+
this.matchesFilter(item.contactId, filters.contactId) &&
|
|
985
|
+
this.matchesFilter(item.senderId, filters.senderId) &&
|
|
986
|
+
this.matchesFilter(item.xpertId, filters.xpertId));
|
|
987
|
+
}
|
|
988
|
+
matchesLogFilters(log, filters) {
|
|
989
|
+
const chatType = this.normalizeChatType(filters.chatType);
|
|
990
|
+
const level = this.normalizeListSearch(filters.level);
|
|
991
|
+
if (chatType && log.chatType !== chatType) {
|
|
992
|
+
return false;
|
|
993
|
+
}
|
|
994
|
+
if (level === 'error' && !(log.status === 'failed' || log.error)) {
|
|
995
|
+
return false;
|
|
996
|
+
}
|
|
997
|
+
if (level === 'info' && (log.status === 'failed' || log.error)) {
|
|
998
|
+
return false;
|
|
999
|
+
}
|
|
1000
|
+
return (this.matchesFilter(log.integrationId, filters.integrationId) &&
|
|
1001
|
+
this.matchesFilter(log.uuid, filters.uuid) &&
|
|
1002
|
+
this.matchesFilter(log.ownerWxid, filters.ownerWxid) &&
|
|
1003
|
+
this.matchesFilter(log.contactId, filters.contactId) &&
|
|
1004
|
+
this.matchesFilter(log.senderId, filters.senderId) &&
|
|
1005
|
+
this.matchesFilter(log.xpertId, filters.xpertId) &&
|
|
1006
|
+
this.matchesFilter(log.conversationId, filters.conversationId));
|
|
1007
|
+
}
|
|
1008
|
+
normalizeFilters(value) {
|
|
1009
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
1010
|
+
}
|
|
1011
|
+
matchesFilter(value, filter) {
|
|
1012
|
+
const normalizedFilter = this.normalizeListSearch(filter);
|
|
1013
|
+
if (!normalizedFilter || normalizedFilter === 'all') {
|
|
1014
|
+
return true;
|
|
1015
|
+
}
|
|
1016
|
+
return Boolean(this.normalizeListSearch(value)?.includes(normalizedFilter));
|
|
1017
|
+
}
|
|
1018
|
+
normalizeBooleanFilter(value) {
|
|
1019
|
+
if (typeof value === 'boolean') {
|
|
1020
|
+
return value;
|
|
1021
|
+
}
|
|
1022
|
+
const normalized = this.normalizeListSearch(value);
|
|
1023
|
+
if (!normalized || normalized === 'all') {
|
|
1024
|
+
return null;
|
|
1025
|
+
}
|
|
1026
|
+
if (['true', '1', 'yes', 'enabled'].includes(normalized)) {
|
|
1027
|
+
return true;
|
|
1028
|
+
}
|
|
1029
|
+
if (['false', '0', 'no', 'disabled'].includes(normalized)) {
|
|
1030
|
+
return false;
|
|
1031
|
+
}
|
|
1032
|
+
return null;
|
|
1033
|
+
}
|
|
1034
|
+
normalizeDirection(value) {
|
|
1035
|
+
const normalized = this.normalizeListSearch(value);
|
|
1036
|
+
return normalized === 'inbound' || normalized === 'outbound' || normalized === 'system' ? normalized : null;
|
|
1037
|
+
}
|
|
1038
|
+
normalizeLogStatus(value) {
|
|
1039
|
+
const normalized = this.normalizeListSearch(value);
|
|
1040
|
+
return ['received', 'dispatched', 'sent', 'skipped', 'failed'].includes(normalized || '')
|
|
1041
|
+
? normalized
|
|
1042
|
+
: null;
|
|
1043
|
+
}
|
|
1044
|
+
normalizeChatType(value) {
|
|
1045
|
+
const normalized = this.normalizeListSearch(value);
|
|
1046
|
+
return normalized === 'private' || normalized === 'group' ? normalized : null;
|
|
1047
|
+
}
|
|
1048
|
+
matchesConversationSearch(item, search) {
|
|
1049
|
+
if (!search) {
|
|
1050
|
+
return true;
|
|
1051
|
+
}
|
|
1052
|
+
return [item.integrationId, item.uuid, item.contactId, item.senderId, item.xpertId, item.conversationId].some((value) => this.normalizeListSearch(value)?.includes(search));
|
|
1053
|
+
}
|
|
1054
|
+
matchesLogSearch(log, search) {
|
|
1055
|
+
if (!search) {
|
|
1056
|
+
return true;
|
|
1057
|
+
}
|
|
1058
|
+
return [
|
|
1059
|
+
log.integrationId,
|
|
1060
|
+
log.uuid,
|
|
1061
|
+
log.contactId,
|
|
1062
|
+
log.senderId,
|
|
1063
|
+
log.content,
|
|
1064
|
+
log.error,
|
|
1065
|
+
log.status
|
|
1066
|
+
].some((value) => this.normalizeListSearch(value)?.includes(search));
|
|
1067
|
+
}
|
|
1068
|
+
toConversationListItem(binding, integrationId) {
|
|
1069
|
+
const parsed = parseWechatPersonalConversationUserKey(binding.conversationUserKey);
|
|
1070
|
+
if (!parsed || parsed.integrationId !== integrationId) {
|
|
1071
|
+
return null;
|
|
1072
|
+
}
|
|
1073
|
+
return {
|
|
1074
|
+
id: binding.id,
|
|
1075
|
+
integrationId: parsed.integrationId,
|
|
1076
|
+
uuid: parsed.uuid,
|
|
1077
|
+
contactId: parsed.contactId,
|
|
1078
|
+
senderId: parsed.senderId,
|
|
1079
|
+
chatType: parsed.contactId.endsWith('@chatroom') ? 'group' : 'private',
|
|
1080
|
+
xpertId: binding.xpertId,
|
|
1081
|
+
conversationId: binding.conversationId,
|
|
1082
|
+
updatedAt: this.normalizeDate(binding.lastActiveAt) ?? this.normalizeDate(binding.updatedAt) ?? null
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
normalizePositiveInt(value) {
|
|
1086
|
+
if (typeof value === 'number' && Number.isInteger(value) && value > 0) {
|
|
1087
|
+
return value;
|
|
1088
|
+
}
|
|
1089
|
+
return null;
|
|
1090
|
+
}
|
|
1091
|
+
normalizePage(value) {
|
|
1092
|
+
if (typeof value === 'number' && Number.isInteger(value) && value > 0) {
|
|
1093
|
+
return value;
|
|
1094
|
+
}
|
|
1095
|
+
return 1;
|
|
1096
|
+
}
|
|
1097
|
+
normalizePageSize(value, fallback = 30) {
|
|
1098
|
+
if (typeof value === 'number' && Number.isInteger(value) && value > 0) {
|
|
1099
|
+
return Math.min(value, 100);
|
|
1100
|
+
}
|
|
1101
|
+
return fallback;
|
|
1102
|
+
}
|
|
1103
|
+
paginateItems(items, page, pageSize) {
|
|
1104
|
+
const start = (page - 1) * pageSize;
|
|
1105
|
+
return {
|
|
1106
|
+
items: items.slice(start, start + pageSize),
|
|
1107
|
+
total: items.length,
|
|
1108
|
+
page,
|
|
1109
|
+
pageSize
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
normalizeListSearch(value) {
|
|
1113
|
+
const normalized = normalizeConversationKey(value);
|
|
1114
|
+
return normalized ? normalized.toLocaleLowerCase() : null;
|
|
1115
|
+
}
|
|
1116
|
+
normalizeDate(value) {
|
|
1117
|
+
if (!value) {
|
|
1118
|
+
return undefined;
|
|
1119
|
+
}
|
|
1120
|
+
if (value instanceof Date) {
|
|
1121
|
+
return Number.isNaN(value.getTime()) ? undefined : value;
|
|
1122
|
+
}
|
|
1123
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
1124
|
+
const date = new Date(value);
|
|
1125
|
+
return Number.isNaN(date.getTime()) ? undefined : date;
|
|
1126
|
+
}
|
|
1127
|
+
return undefined;
|
|
1128
|
+
}
|
|
1129
|
+
resolveSessionTimeoutMs(value) {
|
|
1130
|
+
if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
|
|
1131
|
+
return Math.floor(value) * 1000;
|
|
1132
|
+
}
|
|
1133
|
+
return 3600 * 1000;
|
|
1134
|
+
}
|
|
1135
|
+
isConversationExpired(lastActiveAt, sessionTimeoutMs) {
|
|
1136
|
+
if (!lastActiveAt) {
|
|
1137
|
+
return false;
|
|
1138
|
+
}
|
|
1139
|
+
return Date.now() - lastActiveAt.getTime() > sessionTimeoutMs;
|
|
1140
|
+
}
|
|
1141
|
+
parseNewSessionCommand(input) {
|
|
1142
|
+
const trimmedInput = input.trim();
|
|
1143
|
+
if (!trimmedInput.startsWith('/new')) {
|
|
1144
|
+
return {
|
|
1145
|
+
matched: false,
|
|
1146
|
+
input
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
const nextCharacter = trimmedInput.charAt('/new'.length);
|
|
1150
|
+
if (nextCharacter && !/\s/.test(nextCharacter)) {
|
|
1151
|
+
return {
|
|
1152
|
+
matched: false,
|
|
1153
|
+
input
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
return {
|
|
1157
|
+
matched: true,
|
|
1158
|
+
input: trimmedInput.slice('/new'.length).trim()
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
getNewConversationStartedText(language) {
|
|
1162
|
+
return language === 'en'
|
|
1163
|
+
? 'A new conversation has started. Please continue sending your message.'
|
|
1164
|
+
: '已开启新会话,请继续发送消息。';
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
1167
|
+
WechatPersonalConversationService = WechatPersonalConversationService_1 = __decorate([
|
|
1168
|
+
Injectable(),
|
|
1169
|
+
__param(2, Inject(CACHE_MANAGER)),
|
|
1170
|
+
__param(3, InjectRepository(WechatPersonalConversationBindingEntity)),
|
|
1171
|
+
__param(4, InjectRepository(WechatPersonalAccountEntity)),
|
|
1172
|
+
__param(5, InjectRepository(WechatPersonalMessageLogEntity)),
|
|
1173
|
+
__param(6, Inject(WECHAT_PERSONAL_PLUGIN_CONTEXT)),
|
|
1174
|
+
__metadata("design:paramtypes", [WechatPersonalChannelStrategy,
|
|
1175
|
+
WechatPersonalTriggerStrategy, Object, Repository,
|
|
1176
|
+
Repository,
|
|
1177
|
+
Repository, Object])
|
|
1178
|
+
], WechatPersonalConversationService);
|
|
1179
|
+
export { WechatPersonalConversationService };
|