@soimy/dingtalk 3.3.0 → 3.4.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 +141 -12
- package/package.json +2 -2
- package/src/access-control.ts +65 -0
- package/src/ack-reaction/dynamic-ack-reaction-controller.ts +271 -0
- package/src/ack-reaction/dynamic-ack-reaction-events.ts +123 -0
- package/src/ack-reaction/dynamic-ack-reaction-progress.ts +59 -0
- package/src/ack-reaction-classifier.ts +17 -4
- package/src/ack-reaction-service.ts +66 -19
- package/src/attachment-text-extractor.ts +2 -1
- package/src/card-service.ts +145 -257
- package/src/channel.ts +60 -28
- package/src/config-schema.ts +28 -6
- package/src/config.ts +29 -5
- package/src/inbound-handler.ts +694 -520
- package/src/message-context-store.ts +787 -0
- package/src/message-utils.ts +221 -42
- package/src/messaging/quoted-context.ts +269 -0
- package/src/messaging/quoted-ref.ts +97 -0
- package/src/onboarding.ts +62 -5
- package/src/reply-strategy-card.ts +225 -0
- package/src/reply-strategy-markdown.ts +55 -0
- package/src/reply-strategy-with-reaction.ts +190 -0
- package/src/reply-strategy.ts +72 -0
- package/src/send-service.ts +140 -52
- package/src/targeting/agent-name-matcher.ts +148 -0
- package/src/targeting/agent-routing.ts +181 -0
- package/src/targeting/target-directory-adapter.ts +151 -0
- package/src/targeting/target-directory-store.ts +396 -0
- package/src/targeting/target-input.ts +62 -0
- package/src/types.ts +114 -9
- package/src/quote-journal.ts +0 -242
- package/src/quoted-msg-cache.ts +0 -226
package/src/quoted-msg-cache.ts
DELETED
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
import { readNamespaceJson, writeNamespaceJsonAtomic } from "./persistence-store";
|
|
2
|
-
|
|
3
|
-
const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
4
|
-
const MAX_ENTRIES_PER_CONVERSATION = 100;
|
|
5
|
-
const MAX_CONVERSATIONS = 1000;
|
|
6
|
-
const QUOTED_MSG_NAMESPACE = "quoted.msg-download-code";
|
|
7
|
-
|
|
8
|
-
export interface DownloadCodeCacheEntry {
|
|
9
|
-
downloadCode?: string;
|
|
10
|
-
msgType: string;
|
|
11
|
-
createdAt: number;
|
|
12
|
-
expiresAt: number;
|
|
13
|
-
spaceId?: string;
|
|
14
|
-
fileId?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface ConversationBucket {
|
|
18
|
-
entries: Map<string, DownloadCodeCacheEntry>;
|
|
19
|
-
lastActiveAt: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface PersistedConversationBucket {
|
|
23
|
-
updatedAt: number;
|
|
24
|
-
entries: Record<string, DownloadCodeCacheEntry>;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function isValidPersistedEntry(entry: unknown): entry is DownloadCodeCacheEntry {
|
|
28
|
-
if (!entry || typeof entry !== "object") {
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const candidate = entry as Partial<DownloadCodeCacheEntry>;
|
|
33
|
-
return (
|
|
34
|
-
typeof candidate.msgType === "string" &&
|
|
35
|
-
candidate.msgType.length > 0 &&
|
|
36
|
-
typeof candidate.createdAt === "number" &&
|
|
37
|
-
Number.isFinite(candidate.createdAt) &&
|
|
38
|
-
typeof candidate.expiresAt === "number" &&
|
|
39
|
-
Number.isFinite(candidate.expiresAt) &&
|
|
40
|
-
((typeof candidate.downloadCode === "string" && candidate.downloadCode.length > 0) ||
|
|
41
|
-
(typeof candidate.spaceId === "string" &&
|
|
42
|
-
candidate.spaceId.length > 0 &&
|
|
43
|
-
typeof candidate.fileId === "string" &&
|
|
44
|
-
candidate.fileId.length > 0))
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const store = new Map<string, ConversationBucket>();
|
|
49
|
-
|
|
50
|
-
function getBucket(conversationId: string): ConversationBucket | undefined {
|
|
51
|
-
return store.get(conversationId);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function getOrCreateBucket(conversationId: string): ConversationBucket {
|
|
55
|
-
let bucket = store.get(conversationId);
|
|
56
|
-
if (!bucket) {
|
|
57
|
-
bucket = { entries: new Map(), lastActiveAt: Date.now() };
|
|
58
|
-
store.set(conversationId, bucket);
|
|
59
|
-
evictConversationsIfNeeded();
|
|
60
|
-
}
|
|
61
|
-
bucket.lastActiveAt = Date.now();
|
|
62
|
-
return bucket;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function evictConversationsIfNeeded(): void {
|
|
66
|
-
if (store.size <= MAX_CONVERSATIONS) {
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
let oldestKey: string | undefined;
|
|
70
|
-
let oldestTime = Infinity;
|
|
71
|
-
for (const [key, bucket] of store) {
|
|
72
|
-
if (bucket.lastActiveAt < oldestTime) {
|
|
73
|
-
oldestTime = bucket.lastActiveAt;
|
|
74
|
-
oldestKey = key;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
if (oldestKey) {
|
|
78
|
-
store.delete(oldestKey);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function evictEntriesIfNeeded(bucket: ConversationBucket): void {
|
|
83
|
-
if (bucket.entries.size <= MAX_ENTRIES_PER_CONVERSATION) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
let oldestKey: string | undefined;
|
|
87
|
-
let oldestTime = Infinity;
|
|
88
|
-
for (const [key, entry] of bucket.entries) {
|
|
89
|
-
if (entry.createdAt < oldestTime) {
|
|
90
|
-
oldestTime = entry.createdAt;
|
|
91
|
-
oldestKey = key;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
if (oldestKey) {
|
|
95
|
-
bucket.entries.delete(oldestKey);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function purgeExpiredEntries(bucket: ConversationBucket): void {
|
|
100
|
-
const now = Date.now();
|
|
101
|
-
for (const [key, entry] of bucket.entries) {
|
|
102
|
-
if (now >= entry.expiresAt) {
|
|
103
|
-
bucket.entries.delete(key);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function loadFromPersistence(
|
|
109
|
-
accountId: string,
|
|
110
|
-
conversationId: string,
|
|
111
|
-
storePath?: string,
|
|
112
|
-
): ConversationBucket | null {
|
|
113
|
-
if (!storePath) {
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
const persisted = readNamespaceJson<PersistedConversationBucket>(QUOTED_MSG_NAMESPACE, {
|
|
117
|
-
storePath,
|
|
118
|
-
scope: { accountId, conversationId },
|
|
119
|
-
format: "json",
|
|
120
|
-
fallback: { updatedAt: 0, entries: {} },
|
|
121
|
-
});
|
|
122
|
-
const keys = Object.keys(persisted.entries || {});
|
|
123
|
-
if (keys.length === 0) {
|
|
124
|
-
return null;
|
|
125
|
-
}
|
|
126
|
-
const bucket: ConversationBucket = {
|
|
127
|
-
entries: new Map<string, DownloadCodeCacheEntry>(),
|
|
128
|
-
lastActiveAt: Date.now(),
|
|
129
|
-
};
|
|
130
|
-
for (const key of keys) {
|
|
131
|
-
const entry = persisted.entries[key];
|
|
132
|
-
if (!isValidPersistedEntry(entry)) {
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
bucket.entries.set(key, entry);
|
|
136
|
-
}
|
|
137
|
-
purgeExpiredEntries(bucket);
|
|
138
|
-
evictEntriesIfNeeded(bucket);
|
|
139
|
-
return bucket;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function persistBucket(
|
|
143
|
-
accountId: string,
|
|
144
|
-
conversationId: string,
|
|
145
|
-
bucket: ConversationBucket,
|
|
146
|
-
storePath?: string,
|
|
147
|
-
): void {
|
|
148
|
-
if (!storePath) {
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
const entries: Record<string, DownloadCodeCacheEntry> = {};
|
|
152
|
-
for (const [msgId, entry] of bucket.entries.entries()) {
|
|
153
|
-
entries[msgId] = entry;
|
|
154
|
-
}
|
|
155
|
-
writeNamespaceJsonAtomic(QUOTED_MSG_NAMESPACE, {
|
|
156
|
-
storePath,
|
|
157
|
-
scope: { accountId, conversationId },
|
|
158
|
-
format: "json",
|
|
159
|
-
data: {
|
|
160
|
-
updatedAt: Date.now(),
|
|
161
|
-
entries,
|
|
162
|
-
} satisfies PersistedConversationBucket,
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export function cacheInboundDownloadCode(
|
|
167
|
-
accountId: string,
|
|
168
|
-
conversationId: string,
|
|
169
|
-
msgId: string,
|
|
170
|
-
downloadCode: string | undefined,
|
|
171
|
-
msgType: string,
|
|
172
|
-
createdAt: number,
|
|
173
|
-
extra?: { spaceId?: string; fileId?: string; storePath?: string },
|
|
174
|
-
): void {
|
|
175
|
-
if (!downloadCode && !extra?.spaceId && !extra?.fileId) {
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
const scopedKey = `${accountId}:${conversationId}`;
|
|
179
|
-
const bucket = getOrCreateBucket(scopedKey);
|
|
180
|
-
purgeExpiredEntries(bucket);
|
|
181
|
-
bucket.entries.set(msgId, {
|
|
182
|
-
downloadCode,
|
|
183
|
-
msgType,
|
|
184
|
-
createdAt,
|
|
185
|
-
expiresAt: Date.now() + DEFAULT_TTL_MS,
|
|
186
|
-
spaceId: extra?.spaceId,
|
|
187
|
-
fileId: extra?.fileId,
|
|
188
|
-
});
|
|
189
|
-
evictEntriesIfNeeded(bucket);
|
|
190
|
-
persistBucket(accountId, conversationId, bucket, extra?.storePath);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export function getCachedDownloadCode(
|
|
194
|
-
accountId: string,
|
|
195
|
-
conversationId: string,
|
|
196
|
-
msgId: string,
|
|
197
|
-
storePath?: string,
|
|
198
|
-
): DownloadCodeCacheEntry | null {
|
|
199
|
-
const scopedKey = `${accountId}:${conversationId}`;
|
|
200
|
-
let bucket = getBucket(scopedKey);
|
|
201
|
-
if (!bucket && storePath) {
|
|
202
|
-
const loaded = loadFromPersistence(accountId, conversationId, storePath);
|
|
203
|
-
if (loaded) {
|
|
204
|
-
store.set(scopedKey, loaded);
|
|
205
|
-
evictConversationsIfNeeded();
|
|
206
|
-
bucket = loaded;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
if (!bucket) {
|
|
210
|
-
return null;
|
|
211
|
-
}
|
|
212
|
-
const entry = bucket.entries.get(msgId);
|
|
213
|
-
if (!entry) {
|
|
214
|
-
return null;
|
|
215
|
-
}
|
|
216
|
-
if (Date.now() >= entry.expiresAt) {
|
|
217
|
-
bucket.entries.delete(msgId);
|
|
218
|
-
return null;
|
|
219
|
-
}
|
|
220
|
-
bucket.lastActiveAt = Date.now();
|
|
221
|
-
return entry;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
export function clearQuotedMsgCacheForTest(): void {
|
|
225
|
-
store.clear();
|
|
226
|
-
}
|