@soimy/dingtalk 3.5.0 → 3.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,464 @@
1
+ import {
2
+ applyManualGlobalLearningRule,
3
+ applyManualSessionLearningNote,
4
+ applyManualTargetLearningRule,
5
+ applyManualTargetsLearningRule,
6
+ applyTargetSetLearningRule,
7
+ createOrUpdateTargetSet,
8
+ deleteManualRule,
9
+ disableManualRule,
10
+ listLearningTargetSets,
11
+ listScopedLearningRules,
12
+ resolveManualForcedReply,
13
+ } from "../feedback-learning-service";
14
+ import {
15
+ formatLearnAppliedReply,
16
+ formatLearnCommandHelp,
17
+ formatLearnDeletedReply,
18
+ formatLearnDisabledReply,
19
+ formatLearnListReply,
20
+ formatOwnerOnlyDeniedReply,
21
+ formatOwnerStatusReply,
22
+ formatTargetSetSavedReply,
23
+ formatWhereAmIReply,
24
+ formatWhoAmIReply,
25
+ isLearningOwner,
26
+ parseLearnCommand,
27
+ } from "../learning-command-service";
28
+ import {
29
+ formatSessionAliasBoundReply,
30
+ formatSessionAliasClearedReply,
31
+ formatSessionAliasReply,
32
+ formatSessionAliasSetReply,
33
+ formatSessionAliasUnboundReply,
34
+ formatSessionAliasValidationErrorReply,
35
+ parseSessionCommand,
36
+ validateSessionAlias,
37
+ } from "../session-command-service";
38
+ import type { SessionPeerSourceKind } from "../session-peer-store";
39
+ import type { DingTalkConfig, HandleDingTalkMessageParams, MessageContent } from "../types";
40
+
41
+ type InboundCommandDispatchParams = {
42
+ cfg: HandleDingTalkMessageParams["cfg"];
43
+ accountId: string;
44
+ dingtalkConfig: DingTalkConfig;
45
+ senderId: string;
46
+ isDirect: boolean;
47
+ extractedText: string;
48
+ messageType: MessageContent["messageType"];
49
+ data: {
50
+ conversationId: string;
51
+ senderId?: string;
52
+ senderStaffId?: string;
53
+ };
54
+ accountStorePath: string;
55
+ currentSessionSourceKind: SessionPeerSourceKind;
56
+ currentSessionSourceId: string;
57
+ peerIdOverride?: string;
58
+ sessionPeer: {
59
+ peerId: string;
60
+ };
61
+ sendReply: (text: string) => Promise<void>;
62
+ clearSessionPeerOverride: (params: {
63
+ storePath: string;
64
+ accountId: string;
65
+ sourceKind: SessionPeerSourceKind;
66
+ sourceId: string;
67
+ }) => boolean;
68
+ setSessionPeerOverride: (params: {
69
+ storePath: string;
70
+ accountId: string;
71
+ sourceKind: SessionPeerSourceKind;
72
+ sourceId: string;
73
+ peerId: string;
74
+ }) => void;
75
+ };
76
+
77
+ export async function handleInboundCommandDispatch(
78
+ params: InboundCommandDispatchParams,
79
+ ): Promise<boolean> {
80
+ const parsedLearnCommand = parseLearnCommand(params.extractedText);
81
+ const parsedSessionCommand = parseSessionCommand(params.extractedText);
82
+ const isOwner = isLearningOwner({
83
+ cfg: params.cfg,
84
+ config: params.dingtalkConfig,
85
+ senderId: params.senderId,
86
+ rawSenderId: params.data.senderId,
87
+ });
88
+
89
+ if (params.isDirect && parsedLearnCommand.scope === "whoami") {
90
+ await params.sendReply(
91
+ formatWhoAmIReply({
92
+ senderId: params.senderId,
93
+ rawSenderId: params.data.senderId,
94
+ senderStaffId: params.data.senderStaffId,
95
+ isOwner,
96
+ }),
97
+ );
98
+ return true;
99
+ }
100
+
101
+ if (parsedLearnCommand.scope === "whereami") {
102
+ await params.sendReply(
103
+ formatWhereAmIReply({
104
+ conversationId: params.data.conversationId,
105
+ conversationType: params.isDirect ? "dm" : "group",
106
+ peerId: params.sessionPeer.peerId,
107
+ }),
108
+ );
109
+ return true;
110
+ }
111
+
112
+ if (params.isDirect && parsedLearnCommand.scope === "owner-status") {
113
+ await params.sendReply(
114
+ formatOwnerStatusReply({
115
+ senderId: params.senderId,
116
+ rawSenderId: params.data.senderId,
117
+ isOwner,
118
+ }),
119
+ );
120
+ return true;
121
+ }
122
+
123
+ if (parsedLearnCommand.scope === "help") {
124
+ await params.sendReply(formatLearnCommandHelp());
125
+ return true;
126
+ }
127
+
128
+ if (
129
+ (parsedLearnCommand.scope === "global" ||
130
+ parsedLearnCommand.scope === "session" ||
131
+ parsedLearnCommand.scope === "here" ||
132
+ parsedLearnCommand.scope === "target" ||
133
+ parsedLearnCommand.scope === "targets" ||
134
+ parsedLearnCommand.scope === "list" ||
135
+ parsedLearnCommand.scope === "disable" ||
136
+ parsedLearnCommand.scope === "delete" ||
137
+ parsedLearnCommand.scope === "target-set-create" ||
138
+ parsedLearnCommand.scope === "target-set-apply" ||
139
+ parsedSessionCommand.scope === "session-alias-show" ||
140
+ parsedSessionCommand.scope === "session-alias-set" ||
141
+ parsedSessionCommand.scope === "session-alias-clear" ||
142
+ parsedSessionCommand.scope === "session-alias-bind" ||
143
+ parsedSessionCommand.scope === "session-alias-unbind") &&
144
+ !isOwner
145
+ ) {
146
+ await params.sendReply(formatOwnerOnlyDeniedReply());
147
+ return true;
148
+ }
149
+
150
+ if (isOwner) {
151
+ if (parsedSessionCommand.scope === "session-alias-show") {
152
+ await params.sendReply(
153
+ formatSessionAliasReply({
154
+ sourceKind: params.currentSessionSourceKind,
155
+ sourceId: params.currentSessionSourceId,
156
+ peerId: params.sessionPeer.peerId,
157
+ aliasSource: params.peerIdOverride ? "override" : "default",
158
+ }),
159
+ );
160
+ return true;
161
+ }
162
+
163
+ if (parsedSessionCommand.scope === "session-alias-set" && parsedSessionCommand.peerId) {
164
+ const aliasValidationError = validateSessionAlias(parsedSessionCommand.peerId);
165
+ if (aliasValidationError) {
166
+ await params.sendReply(formatSessionAliasValidationErrorReply(aliasValidationError));
167
+ return true;
168
+ }
169
+ params.setSessionPeerOverride({
170
+ storePath: params.accountStorePath,
171
+ accountId: params.accountId,
172
+ sourceKind: params.currentSessionSourceKind,
173
+ sourceId: params.currentSessionSourceId,
174
+ peerId: parsedSessionCommand.peerId,
175
+ });
176
+ await params.sendReply(
177
+ formatSessionAliasSetReply({
178
+ sourceKind: params.currentSessionSourceKind,
179
+ sourceId: params.currentSessionSourceId,
180
+ peerId: parsedSessionCommand.peerId,
181
+ }),
182
+ );
183
+ return true;
184
+ }
185
+
186
+ if (parsedSessionCommand.scope === "session-alias-clear") {
187
+ params.clearSessionPeerOverride({
188
+ storePath: params.accountStorePath,
189
+ accountId: params.accountId,
190
+ sourceKind: params.currentSessionSourceKind,
191
+ sourceId: params.currentSessionSourceId,
192
+ });
193
+ await params.sendReply(
194
+ formatSessionAliasClearedReply({
195
+ sourceKind: params.currentSessionSourceKind,
196
+ sourceId: params.currentSessionSourceId,
197
+ }),
198
+ );
199
+ return true;
200
+ }
201
+
202
+ if (
203
+ parsedSessionCommand.scope === "session-alias-bind" &&
204
+ parsedSessionCommand.sourceKind &&
205
+ parsedSessionCommand.sourceId &&
206
+ parsedSessionCommand.peerId
207
+ ) {
208
+ const aliasValidationError = validateSessionAlias(parsedSessionCommand.peerId);
209
+ if (aliasValidationError) {
210
+ await params.sendReply(formatSessionAliasValidationErrorReply(aliasValidationError));
211
+ return true;
212
+ }
213
+ params.setSessionPeerOverride({
214
+ storePath: params.accountStorePath,
215
+ accountId: params.accountId,
216
+ sourceKind: parsedSessionCommand.sourceKind,
217
+ sourceId: parsedSessionCommand.sourceId,
218
+ peerId: parsedSessionCommand.peerId,
219
+ });
220
+ await params.sendReply(
221
+ formatSessionAliasBoundReply({
222
+ sourceKind: parsedSessionCommand.sourceKind,
223
+ sourceId: parsedSessionCommand.sourceId,
224
+ peerId: parsedSessionCommand.peerId,
225
+ }),
226
+ );
227
+ return true;
228
+ }
229
+
230
+ if (
231
+ parsedSessionCommand.scope === "session-alias-unbind" &&
232
+ parsedSessionCommand.sourceKind &&
233
+ parsedSessionCommand.sourceId
234
+ ) {
235
+ const existed = params.clearSessionPeerOverride({
236
+ storePath: params.accountStorePath,
237
+ accountId: params.accountId,
238
+ sourceKind: parsedSessionCommand.sourceKind,
239
+ sourceId: parsedSessionCommand.sourceId,
240
+ });
241
+ await params.sendReply(
242
+ formatSessionAliasUnboundReply({
243
+ sourceKind: parsedSessionCommand.sourceKind,
244
+ sourceId: parsedSessionCommand.sourceId,
245
+ existed,
246
+ }),
247
+ );
248
+ return true;
249
+ }
250
+
251
+ if (parsedLearnCommand.scope === "global" && parsedLearnCommand.instruction) {
252
+ const applied = applyManualGlobalLearningRule({
253
+ storePath: params.accountStorePath,
254
+ accountId: params.accountId,
255
+ instruction: parsedLearnCommand.instruction,
256
+ });
257
+ await params.sendReply(
258
+ formatLearnAppliedReply({
259
+ scope: "global",
260
+ instruction: parsedLearnCommand.instruction,
261
+ ruleId: applied?.ruleId,
262
+ }),
263
+ );
264
+ return true;
265
+ }
266
+
267
+ if (parsedLearnCommand.scope === "session" && parsedLearnCommand.instruction) {
268
+ applyManualSessionLearningNote({
269
+ storePath: params.accountStorePath,
270
+ accountId: params.accountId,
271
+ targetId: params.data.conversationId,
272
+ instruction: parsedLearnCommand.instruction,
273
+ });
274
+ await params.sendReply(
275
+ formatLearnAppliedReply({
276
+ scope: "session",
277
+ instruction: parsedLearnCommand.instruction,
278
+ }),
279
+ );
280
+ return true;
281
+ }
282
+
283
+ if (parsedLearnCommand.scope === "here" && parsedLearnCommand.instruction) {
284
+ const applied = applyManualTargetLearningRule({
285
+ storePath: params.accountStorePath,
286
+ accountId: params.accountId,
287
+ targetId: params.data.conversationId,
288
+ instruction: parsedLearnCommand.instruction,
289
+ });
290
+ await params.sendReply(
291
+ formatLearnAppliedReply({
292
+ scope: "target",
293
+ targetId: params.data.conversationId,
294
+ instruction: parsedLearnCommand.instruction,
295
+ ruleId: applied?.ruleId,
296
+ }),
297
+ );
298
+ return true;
299
+ }
300
+
301
+ if (
302
+ parsedLearnCommand.scope === "target" &&
303
+ parsedLearnCommand.targetId &&
304
+ parsedLearnCommand.instruction
305
+ ) {
306
+ const applied = applyManualTargetLearningRule({
307
+ storePath: params.accountStorePath,
308
+ accountId: params.accountId,
309
+ targetId: parsedLearnCommand.targetId,
310
+ instruction: parsedLearnCommand.instruction,
311
+ });
312
+ await params.sendReply(
313
+ formatLearnAppliedReply({
314
+ scope: "target",
315
+ targetId: parsedLearnCommand.targetId,
316
+ instruction: parsedLearnCommand.instruction,
317
+ ruleId: applied?.ruleId,
318
+ }),
319
+ );
320
+ return true;
321
+ }
322
+
323
+ if (
324
+ parsedLearnCommand.scope === "targets" &&
325
+ parsedLearnCommand.targetIds?.length &&
326
+ parsedLearnCommand.instruction
327
+ ) {
328
+ const applied = applyManualTargetsLearningRule({
329
+ storePath: params.accountStorePath,
330
+ accountId: params.accountId,
331
+ targetIds: parsedLearnCommand.targetIds,
332
+ instruction: parsedLearnCommand.instruction,
333
+ });
334
+ await params.sendReply(
335
+ formatLearnAppliedReply({
336
+ scope: "targets",
337
+ targetIds: parsedLearnCommand.targetIds,
338
+ instruction: parsedLearnCommand.instruction,
339
+ ruleId: applied[0]?.ruleId,
340
+ }),
341
+ );
342
+ return true;
343
+ }
344
+
345
+ if (
346
+ parsedLearnCommand.scope === "target-set-create" &&
347
+ parsedLearnCommand.setName &&
348
+ parsedLearnCommand.targetIds?.length
349
+ ) {
350
+ const saved = createOrUpdateTargetSet({
351
+ storePath: params.accountStorePath,
352
+ accountId: params.accountId,
353
+ name: parsedLearnCommand.setName,
354
+ targetIds: parsedLearnCommand.targetIds,
355
+ });
356
+ await params.sendReply(
357
+ saved
358
+ ? formatTargetSetSavedReply({
359
+ setName: parsedLearnCommand.setName,
360
+ targetIds: parsedLearnCommand.targetIds,
361
+ })
362
+ : "目标组保存失败,请检查名称和目标列表。",
363
+ );
364
+ return true;
365
+ }
366
+
367
+ if (
368
+ parsedLearnCommand.scope === "target-set-apply" &&
369
+ parsedLearnCommand.setName &&
370
+ parsedLearnCommand.instruction
371
+ ) {
372
+ const applied = applyTargetSetLearningRule({
373
+ storePath: params.accountStorePath,
374
+ accountId: params.accountId,
375
+ name: parsedLearnCommand.setName,
376
+ instruction: parsedLearnCommand.instruction,
377
+ });
378
+ await params.sendReply(
379
+ applied.length > 0
380
+ ? formatLearnAppliedReply({
381
+ scope: "target-set",
382
+ setName: parsedLearnCommand.setName,
383
+ targetIds: applied.map((item) => item.targetId),
384
+ instruction: parsedLearnCommand.instruction,
385
+ ruleId: applied[0]?.ruleId,
386
+ })
387
+ : `未找到目标组 \`${parsedLearnCommand.setName}\`,或该目标组为空。`,
388
+ );
389
+ return true;
390
+ }
391
+
392
+ if (parsedLearnCommand.scope === "list") {
393
+ const rules = listScopedLearningRules({
394
+ storePath: params.accountStorePath,
395
+ accountId: params.accountId,
396
+ })
397
+ .slice(0, 20)
398
+ .map((rule) => {
399
+ const scope = rule.scope === "target" ? `target(${rule.targetId})` : "global";
400
+ const status = rule.enabled ? "enabled" : "disabled";
401
+ return `- [${scope}] ${rule.ruleId} (${status}) => ${rule.instruction}`;
402
+ });
403
+ const targetSets = listLearningTargetSets({
404
+ storePath: params.accountStorePath,
405
+ accountId: params.accountId,
406
+ })
407
+ .slice(0, 10)
408
+ .map((targetSet) => `- [target-set] ${targetSet.name} => ${targetSet.targetIds.join(", ")}`);
409
+ await params.sendReply(formatLearnListReply([...rules, ...targetSets]));
410
+ return true;
411
+ }
412
+
413
+ if (parsedLearnCommand.scope === "disable" && parsedLearnCommand.ruleId) {
414
+ const result = disableManualRule({
415
+ storePath: params.accountStorePath,
416
+ accountId: params.accountId,
417
+ ruleId: parsedLearnCommand.ruleId,
418
+ });
419
+ await params.sendReply(
420
+ formatLearnDisabledReply({
421
+ ruleId: parsedLearnCommand.ruleId,
422
+ existed: result.existed,
423
+ scope: result.scope,
424
+ targetId: result.targetId,
425
+ }),
426
+ );
427
+ return true;
428
+ }
429
+
430
+ if (parsedLearnCommand.scope === "delete" && parsedLearnCommand.ruleId) {
431
+ const result = deleteManualRule({
432
+ storePath: params.accountStorePath,
433
+ accountId: params.accountId,
434
+ ruleId: parsedLearnCommand.ruleId,
435
+ });
436
+ await params.sendReply(
437
+ formatLearnDeletedReply({
438
+ ruleId: parsedLearnCommand.ruleId,
439
+ existed: result.existed,
440
+ scope: result.scope,
441
+ targetId: result.targetId,
442
+ }),
443
+ );
444
+ return true;
445
+ }
446
+ }
447
+
448
+ const forcedContent: MessageContent = {
449
+ text: params.extractedText,
450
+ messageType: params.messageType,
451
+ };
452
+ const manualForcedReply = resolveManualForcedReply({
453
+ storePath: params.accountStorePath,
454
+ accountId: params.accountId,
455
+ targetId: params.data.conversationId,
456
+ content: forcedContent,
457
+ });
458
+ if (manualForcedReply) {
459
+ await params.sendReply(manualForcedReply);
460
+ return true;
461
+ }
462
+
463
+ return false;
464
+ }
@@ -1,4 +1,4 @@
1
- import axios from "axios";
1
+ import axios, { DEFAULT_HTTP_TIMEOUT_MS } from "./http-client";
2
2
  import { getAccessToken } from "./auth";
3
3
  import { getLogger } from "./logger-context";
4
4
  import { getProxyBypassOption } from "./utils";
@@ -103,7 +103,7 @@ export async function createDoc(
103
103
  },
104
104
  {
105
105
  headers,
106
- timeout: 10_000,
106
+ timeout: DEFAULT_HTTP_TIMEOUT_MS,
107
107
  ...getProxyBypassOption(config),
108
108
  },
109
109
  );
@@ -141,7 +141,7 @@ export async function appendToDoc(
141
141
  },
142
142
  {
143
143
  headers,
144
- timeout: 10_000,
144
+ timeout: DEFAULT_HTTP_TIMEOUT_MS,
145
145
  ...getProxyBypassOption(config),
146
146
  },
147
147
  );
@@ -167,7 +167,7 @@ export async function searchDocs(
167
167
  },
168
168
  {
169
169
  headers,
170
- timeout: 10_000,
170
+ timeout: DEFAULT_HTTP_TIMEOUT_MS,
171
171
  ...getProxyBypassOption(config),
172
172
  },
173
173
  );
@@ -189,7 +189,7 @@ export async function listDocs(
189
189
  maxResults: 50,
190
190
  ...(parentId ? { parentDentryId: parentId } : {}),
191
191
  },
192
- timeout: 10_000,
192
+ timeout: DEFAULT_HTTP_TIMEOUT_MS,
193
193
  ...getProxyBypassOption(config),
194
194
  });
195
195
  return Array.isArray(resp.data?.items)
@@ -0,0 +1,20 @@
1
+ import axios from "axios";
2
+ import type { AxiosInstance } from "axios";
3
+
4
+ export const DEFAULT_HTTP_TIMEOUT_MS = 10_000;
5
+
6
+ type AxiosInstanceWithGuards = AxiosInstance & {
7
+ isAxiosError: typeof axios.isAxiosError;
8
+ };
9
+
10
+ // Centralize repo-level axios policy without mutating the global axios singleton
11
+ // that third-party dependencies may also share.
12
+ const httpClient = (
13
+ typeof axios?.create === "function"
14
+ ? axios.create({ timeout: DEFAULT_HTTP_TIMEOUT_MS })
15
+ : axios
16
+ ) as AxiosInstanceWithGuards;
17
+
18
+ httpClient.isAxiosError = axios.isAxiosError;
19
+
20
+ export default httpClient;