@max1874/feishu 0.2.28 → 0.3.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/package.json +1 -1
- package/src/accounts.ts +30 -7
- package/src/bot.ts +3 -1
- package/src/channel.ts +90 -23
- package/src/client.ts +13 -15
- package/src/config-schema.ts +16 -0
- package/src/monitor.ts +61 -39
- package/src/types.ts +2 -1
package/package.json
CHANGED
package/src/accounts.ts
CHANGED
|
@@ -21,16 +21,34 @@ export function resolveFeishuCredentials(cfg?: FeishuConfig): {
|
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Merge per-account overrides with the base (top-level) feishu config.
|
|
26
|
+
* Account-specific fields (credentials, domain, connectionMode, etc.) override the base.
|
|
27
|
+
*/
|
|
28
|
+
export function mergeFeishuAccountConfig(
|
|
29
|
+
baseCfg: FeishuConfig,
|
|
30
|
+
accountId: string,
|
|
31
|
+
): FeishuConfig {
|
|
32
|
+
if (accountId === DEFAULT_ACCOUNT_ID) return baseCfg;
|
|
33
|
+
const overrides = baseCfg.accounts?.[accountId];
|
|
34
|
+
if (!overrides) return baseCfg;
|
|
35
|
+
// Strip `accounts` from merged result to prevent downstream confusion
|
|
36
|
+
const { accounts: _, ...base } = baseCfg;
|
|
37
|
+
return { ...base, ...overrides };
|
|
38
|
+
}
|
|
39
|
+
|
|
24
40
|
export function resolveFeishuAccount(params: {
|
|
25
41
|
cfg: ClawdbotConfig;
|
|
26
42
|
accountId?: string | null;
|
|
27
43
|
}): ResolvedFeishuAccount {
|
|
28
44
|
const feishuCfg = params.cfg.channels?.feishu as FeishuConfig | undefined;
|
|
29
|
-
const
|
|
30
|
-
const
|
|
45
|
+
const accountId = params.accountId?.trim() || DEFAULT_ACCOUNT_ID;
|
|
46
|
+
const merged = feishuCfg ? mergeFeishuAccountConfig(feishuCfg, accountId) : undefined;
|
|
47
|
+
const enabled = merged?.enabled !== false;
|
|
48
|
+
const creds = resolveFeishuCredentials(merged);
|
|
31
49
|
|
|
32
50
|
return {
|
|
33
|
-
accountId
|
|
51
|
+
accountId,
|
|
34
52
|
enabled,
|
|
35
53
|
configured: Boolean(creds),
|
|
36
54
|
appId: creds?.appId,
|
|
@@ -38,12 +56,17 @@ export function resolveFeishuAccount(params: {
|
|
|
38
56
|
};
|
|
39
57
|
}
|
|
40
58
|
|
|
41
|
-
export function listFeishuAccountIds(
|
|
42
|
-
|
|
59
|
+
export function listFeishuAccountIds(cfg: ClawdbotConfig): string[] {
|
|
60
|
+
const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
|
|
61
|
+
const accounts = feishuCfg?.accounts;
|
|
62
|
+
if (!accounts) return [DEFAULT_ACCOUNT_ID];
|
|
63
|
+
const ids = Object.keys(accounts);
|
|
64
|
+
return ids.length === 0 ? [DEFAULT_ACCOUNT_ID] : ids;
|
|
43
65
|
}
|
|
44
66
|
|
|
45
|
-
export function resolveDefaultFeishuAccountId(
|
|
46
|
-
|
|
67
|
+
export function resolveDefaultFeishuAccountId(cfg: ClawdbotConfig): string {
|
|
68
|
+
const ids = listFeishuAccountIds(cfg);
|
|
69
|
+
return ids[0] ?? DEFAULT_ACCOUNT_ID;
|
|
47
70
|
}
|
|
48
71
|
|
|
49
72
|
export function listEnabledFeishuAccounts(cfg: ClawdbotConfig): ResolvedFeishuAccount[] {
|
package/src/bot.ts
CHANGED
|
@@ -490,8 +490,9 @@ export async function handleFeishuMessage(params: {
|
|
|
490
490
|
botOpenId?: string;
|
|
491
491
|
runtime?: RuntimeEnv;
|
|
492
492
|
chatHistories?: Map<string, HistoryEntry[]>;
|
|
493
|
+
accountId?: string;
|
|
493
494
|
}): Promise<void> {
|
|
494
|
-
const { cfg, event, botOpenId, runtime, chatHistories } = params;
|
|
495
|
+
const { cfg, event, botOpenId, runtime, chatHistories, accountId } = params;
|
|
495
496
|
const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
|
|
496
497
|
const log = runtime?.log ?? console.log;
|
|
497
498
|
const error = runtime?.error ?? console.error;
|
|
@@ -624,6 +625,7 @@ export async function handleFeishuMessage(params: {
|
|
|
624
625
|
const route = core.channel.routing.resolveAgentRoute({
|
|
625
626
|
cfg: routingCfg,
|
|
626
627
|
channel: "feishu",
|
|
628
|
+
accountId,
|
|
627
629
|
peer: {
|
|
628
630
|
kind: isGroup ? "group" : "dm",
|
|
629
631
|
id: isGroup ? ctx.chatId : ctx.senderOpenId,
|
package/src/channel.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ChannelPlugin, ClawdbotConfig } from "openclaw/plugin-sdk";
|
|
2
2
|
import { DEFAULT_ACCOUNT_ID, PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk";
|
|
3
3
|
import type { ResolvedFeishuAccount, FeishuConfig } from "./types.js";
|
|
4
|
-
import { resolveFeishuAccount, resolveFeishuCredentials } from "./accounts.js";
|
|
4
|
+
import { resolveFeishuAccount, resolveFeishuCredentials, listFeishuAccountIds, mergeFeishuAccountConfig } from "./accounts.js";
|
|
5
5
|
import { feishuOutbound } from "./outbound.js";
|
|
6
6
|
import { probeFeishu } from "./probe.js";
|
|
7
7
|
import { resolveFeishuGroupToolPolicy } from "./policy.js";
|
|
@@ -87,24 +87,78 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
|
|
|
87
87
|
chunkMode: { type: "string", enum: ["length", "newline"] },
|
|
88
88
|
mediaMaxMb: { type: "number", minimum: 0 },
|
|
89
89
|
renderMode: { type: "string", enum: ["auto", "raw", "card"] },
|
|
90
|
+
accounts: {
|
|
91
|
+
type: "object",
|
|
92
|
+
additionalProperties: {
|
|
93
|
+
type: "object",
|
|
94
|
+
properties: {
|
|
95
|
+
enabled: { type: "boolean" },
|
|
96
|
+
name: { type: "string" },
|
|
97
|
+
appId: { type: "string" },
|
|
98
|
+
appSecret: { type: "string" },
|
|
99
|
+
encryptKey: { type: "string" },
|
|
100
|
+
verificationToken: { type: "string" },
|
|
101
|
+
domain: { type: "string", enum: ["feishu", "lark"] },
|
|
102
|
+
connectionMode: { type: "string", enum: ["websocket", "webhook"] },
|
|
103
|
+
webhookPath: { type: "string" },
|
|
104
|
+
webhookPort: { type: "integer", minimum: 1 },
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
90
108
|
},
|
|
91
109
|
},
|
|
92
110
|
},
|
|
93
111
|
config: {
|
|
94
|
-
listAccountIds: () =>
|
|
95
|
-
resolveAccount: (cfg) => resolveFeishuAccount({ cfg }),
|
|
96
|
-
defaultAccountId: () => DEFAULT_ACCOUNT_ID,
|
|
97
|
-
setAccountEnabled: ({ cfg, enabled }) =>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
...cfg
|
|
103
|
-
|
|
112
|
+
listAccountIds: (cfg) => listFeishuAccountIds(cfg),
|
|
113
|
+
resolveAccount: (cfg, accountId) => resolveFeishuAccount({ cfg, accountId }),
|
|
114
|
+
defaultAccountId: (cfg) => listFeishuAccountIds(cfg)[0] ?? DEFAULT_ACCOUNT_ID,
|
|
115
|
+
setAccountEnabled: ({ cfg, accountId, enabled }) => {
|
|
116
|
+
const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
|
|
117
|
+
// Per-account enable/disable when using named accounts
|
|
118
|
+
if (accountId && accountId !== DEFAULT_ACCOUNT_ID && feishuCfg?.accounts?.[accountId]) {
|
|
119
|
+
return {
|
|
120
|
+
...cfg,
|
|
121
|
+
channels: {
|
|
122
|
+
...cfg.channels,
|
|
123
|
+
feishu: {
|
|
124
|
+
...feishuCfg,
|
|
125
|
+
accounts: {
|
|
126
|
+
...feishuCfg.accounts,
|
|
127
|
+
[accountId]: { ...feishuCfg.accounts[accountId], enabled },
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
...cfg,
|
|
135
|
+
channels: {
|
|
136
|
+
...cfg.channels,
|
|
137
|
+
feishu: {
|
|
138
|
+
...cfg.channels?.feishu,
|
|
139
|
+
enabled,
|
|
140
|
+
},
|
|
104
141
|
},
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
deleteAccount: ({ cfg }) => {
|
|
142
|
+
};
|
|
143
|
+
},
|
|
144
|
+
deleteAccount: ({ cfg, accountId }) => {
|
|
145
|
+
const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
|
|
146
|
+
// Delete a named account
|
|
147
|
+
if (accountId && accountId !== DEFAULT_ACCOUNT_ID && feishuCfg?.accounts) {
|
|
148
|
+
const nextAccounts = { ...feishuCfg.accounts };
|
|
149
|
+
delete nextAccounts[accountId];
|
|
150
|
+
return {
|
|
151
|
+
...cfg,
|
|
152
|
+
channels: {
|
|
153
|
+
...cfg.channels,
|
|
154
|
+
feishu: {
|
|
155
|
+
...feishuCfg,
|
|
156
|
+
accounts: Object.keys(nextAccounts).length > 0 ? nextAccounts : undefined,
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
// Delete the whole channel (default account)
|
|
108
162
|
const next = { ...cfg } as ClawdbotConfig;
|
|
109
163
|
const nextChannels = { ...cfg.channels };
|
|
110
164
|
delete (nextChannels as Record<string, unknown>).feishu;
|
|
@@ -115,8 +169,7 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
|
|
|
115
169
|
}
|
|
116
170
|
return next;
|
|
117
171
|
},
|
|
118
|
-
isConfigured: (
|
|
119
|
-
Boolean(resolveFeishuCredentials(cfg.channels?.feishu as FeishuConfig | undefined)),
|
|
172
|
+
isConfigured: (account) => account.configured,
|
|
120
173
|
describeAccount: (account) => ({
|
|
121
174
|
accountId: account.accountId,
|
|
122
175
|
enabled: account.enabled,
|
|
@@ -142,7 +195,7 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
|
|
|
142
195
|
},
|
|
143
196
|
},
|
|
144
197
|
setup: {
|
|
145
|
-
resolveAccountId: () => DEFAULT_ACCOUNT_ID,
|
|
198
|
+
resolveAccountId: (cfg) => listFeishuAccountIds(cfg)[0] ?? DEFAULT_ACCOUNT_ID,
|
|
146
199
|
applyAccountConfig: ({ cfg }) => ({
|
|
147
200
|
...cfg,
|
|
148
201
|
channels: {
|
|
@@ -193,8 +246,11 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
|
|
|
193
246
|
probe: snapshot.probe,
|
|
194
247
|
lastProbeAt: snapshot.lastProbeAt ?? null,
|
|
195
248
|
}),
|
|
196
|
-
probeAccount: async ({ cfg }) =>
|
|
197
|
-
|
|
249
|
+
probeAccount: async ({ cfg, accountId }) => {
|
|
250
|
+
const baseCfg = cfg.channels?.feishu as FeishuConfig | undefined;
|
|
251
|
+
const merged = baseCfg ? mergeFeishuAccountConfig(baseCfg, accountId ?? DEFAULT_ACCOUNT_ID) : baseCfg;
|
|
252
|
+
return probeFeishu(merged);
|
|
253
|
+
},
|
|
198
254
|
buildAccountSnapshot: ({ account, runtime, probe }) => ({
|
|
199
255
|
accountId: account.accountId,
|
|
200
256
|
enabled: account.enabled,
|
|
@@ -210,12 +266,23 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
|
|
|
210
266
|
gateway: {
|
|
211
267
|
startAccount: async (ctx) => {
|
|
212
268
|
const { monitorFeishuProvider } = await import("./monitor.js");
|
|
213
|
-
const
|
|
214
|
-
const
|
|
269
|
+
const baseCfg = ctx.cfg.channels?.feishu as FeishuConfig | undefined;
|
|
270
|
+
const merged = baseCfg ? mergeFeishuAccountConfig(baseCfg, ctx.accountId) : baseCfg;
|
|
271
|
+
const port = merged?.webhookPort ?? null;
|
|
272
|
+
|
|
273
|
+
// Inject merged account config so all downstream reads of cfg.channels.feishu get the right values
|
|
274
|
+
const accountCfg: ClawdbotConfig = {
|
|
275
|
+
...ctx.cfg,
|
|
276
|
+
channels: {
|
|
277
|
+
...ctx.cfg.channels,
|
|
278
|
+
feishu: merged,
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
|
|
215
282
|
ctx.setStatus({ accountId: ctx.accountId, port });
|
|
216
|
-
ctx.log?.info(`starting feishu provider (mode: ${
|
|
283
|
+
ctx.log?.info(`starting feishu provider (account: ${ctx.accountId}, mode: ${merged?.connectionMode ?? "websocket"})`);
|
|
217
284
|
return monitorFeishuProvider({
|
|
218
|
-
config:
|
|
285
|
+
config: accountCfg,
|
|
219
286
|
runtime: ctx.runtime,
|
|
220
287
|
abortSignal: ctx.abortSignal,
|
|
221
288
|
accountId: ctx.accountId,
|
package/src/client.ts
CHANGED
|
@@ -2,8 +2,8 @@ import * as Lark from "@larksuiteoapi/node-sdk";
|
|
|
2
2
|
import type { FeishuConfig, FeishuDomain } from "./types.js";
|
|
3
3
|
import { resolveFeishuCredentials } from "./accounts.js";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
type CachedEntry = { client: Lark.Client; appId: string; appSecret: string; domain: FeishuDomain };
|
|
6
|
+
const clientCache = new Map<string, CachedEntry>();
|
|
7
7
|
|
|
8
8
|
function resolveDomain(domain: FeishuDomain) {
|
|
9
9
|
return domain === "lark" ? Lark.Domain.Lark : Lark.Domain.Feishu;
|
|
@@ -15,14 +15,10 @@ export function createFeishuClient(cfg: FeishuConfig): Lark.Client {
|
|
|
15
15
|
throw new Error("Feishu credentials not configured (appId, appSecret required)");
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
cachedConfig.appSecret === creds.appSecret &&
|
|
23
|
-
cachedConfig.domain === creds.domain
|
|
24
|
-
) {
|
|
25
|
-
return cachedClient;
|
|
18
|
+
const cacheKey = `${creds.appId}:${creds.domain}`;
|
|
19
|
+
const cached = clientCache.get(cacheKey);
|
|
20
|
+
if (cached && cached.appSecret === creds.appSecret) {
|
|
21
|
+
return cached.client;
|
|
26
22
|
}
|
|
27
23
|
|
|
28
24
|
const client = new Lark.Client({
|
|
@@ -32,8 +28,7 @@ export function createFeishuClient(cfg: FeishuConfig): Lark.Client {
|
|
|
32
28
|
domain: resolveDomain(creds.domain),
|
|
33
29
|
});
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
cachedConfig = { appId: creds.appId, appSecret: creds.appSecret, domain: creds.domain };
|
|
31
|
+
clientCache.set(cacheKey, { client, appId: creds.appId, appSecret: creds.appSecret, domain: creds.domain });
|
|
37
32
|
|
|
38
33
|
return client;
|
|
39
34
|
}
|
|
@@ -60,7 +55,10 @@ export function createEventDispatcher(cfg: FeishuConfig): Lark.EventDispatcher {
|
|
|
60
55
|
});
|
|
61
56
|
}
|
|
62
57
|
|
|
63
|
-
export function clearClientCache() {
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
export function clearClientCache(accountKey?: string) {
|
|
59
|
+
if (accountKey) {
|
|
60
|
+
clientCache.delete(accountKey);
|
|
61
|
+
} else {
|
|
62
|
+
clientCache.clear();
|
|
63
|
+
}
|
|
66
64
|
}
|
package/src/config-schema.ts
CHANGED
|
@@ -79,6 +79,21 @@ export const FeishuGroupSchema = z
|
|
|
79
79
|
})
|
|
80
80
|
.strict();
|
|
81
81
|
|
|
82
|
+
export const FeishuAccountOverrideSchema = z
|
|
83
|
+
.object({
|
|
84
|
+
enabled: z.boolean().optional(),
|
|
85
|
+
name: z.string().optional(),
|
|
86
|
+
appId: z.string().optional(),
|
|
87
|
+
appSecret: z.string().optional(),
|
|
88
|
+
encryptKey: z.string().optional(),
|
|
89
|
+
verificationToken: z.string().optional(),
|
|
90
|
+
domain: FeishuDomainSchema.optional(),
|
|
91
|
+
connectionMode: FeishuConnectionModeSchema.optional(),
|
|
92
|
+
webhookPath: z.string().optional(),
|
|
93
|
+
webhookPort: z.number().int().positive().optional(),
|
|
94
|
+
})
|
|
95
|
+
.strict();
|
|
96
|
+
|
|
82
97
|
export const FeishuConfigSchema = z
|
|
83
98
|
.object({
|
|
84
99
|
enabled: z.boolean().optional(),
|
|
@@ -111,6 +126,7 @@ export const FeishuConfigSchema = z
|
|
|
111
126
|
replyToMode: ReplyToModeSchema, // "all" = always thread (default), "off" = main chat unless already in thread
|
|
112
127
|
replyToModeByChatType: ReplyToModeByChatTypeSchema, // per-chat-type overrides for replyToMode
|
|
113
128
|
dmScope: DmScopeSchema, // override session.dmScope for this channel (default: per-channel-peer)
|
|
129
|
+
accounts: z.record(z.string(), FeishuAccountOverrideSchema.optional()).optional(),
|
|
114
130
|
})
|
|
115
131
|
.strict()
|
|
116
132
|
.superRefine((value, ctx) => {
|
package/src/monitor.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import * as Lark from "@larksuiteoapi/node-sdk";
|
|
3
3
|
import type { ClawdbotConfig, RuntimeEnv, HistoryEntry } from "openclaw/plugin-sdk";
|
|
4
|
+
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk";
|
|
4
5
|
import type { FeishuConfig } from "./types.js";
|
|
5
6
|
import { createFeishuWSClient, createEventDispatcher } from "./client.js";
|
|
6
7
|
import { resolveFeishuCredentials } from "./accounts.js";
|
|
@@ -14,9 +15,9 @@ export type MonitorFeishuOpts = {
|
|
|
14
15
|
accountId?: string;
|
|
15
16
|
};
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
const wsClients = new Map<string, Lark.WSClient>();
|
|
19
|
+
const httpServers = new Map<string, http.Server>();
|
|
20
|
+
const botOpenIds = new Map<string, string>();
|
|
20
21
|
|
|
21
22
|
async function fetchBotOpenId(cfg: FeishuConfig): Promise<string | undefined> {
|
|
22
23
|
try {
|
|
@@ -33,6 +34,7 @@ export async function monitorFeishuProvider(opts: MonitorFeishuOpts = {}): Promi
|
|
|
33
34
|
throw new Error("Config is required for Feishu monitor");
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
const accountId = opts.accountId ?? DEFAULT_ACCOUNT_ID;
|
|
36
38
|
const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
|
|
37
39
|
const creds = resolveFeishuCredentials(feishuCfg);
|
|
38
40
|
if (!creds) {
|
|
@@ -43,17 +45,22 @@ export async function monitorFeishuProvider(opts: MonitorFeishuOpts = {}): Promi
|
|
|
43
45
|
const error = opts.runtime?.error ?? console.error;
|
|
44
46
|
|
|
45
47
|
if (feishuCfg) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
const openId = await fetchBotOpenId(feishuCfg);
|
|
49
|
+
if (openId) {
|
|
50
|
+
botOpenIds.set(accountId, openId);
|
|
51
|
+
} else {
|
|
52
|
+
botOpenIds.delete(accountId);
|
|
53
|
+
}
|
|
54
|
+
log(`feishu[${accountId}]: bot open_id resolved: ${openId ?? "unknown"}`);
|
|
48
55
|
}
|
|
49
56
|
|
|
50
57
|
const connectionMode = feishuCfg?.connectionMode ?? "websocket";
|
|
51
58
|
|
|
52
59
|
if (connectionMode === "websocket") {
|
|
53
|
-
return monitorWebSocket({ cfg, feishuCfg: feishuCfg!, runtime: opts.runtime, abortSignal: opts.abortSignal });
|
|
60
|
+
return monitorWebSocket({ cfg, feishuCfg: feishuCfg!, runtime: opts.runtime, abortSignal: opts.abortSignal, accountId });
|
|
54
61
|
}
|
|
55
62
|
|
|
56
|
-
return monitorWebhook({ cfg, feishuCfg: feishuCfg!, runtime: opts.runtime, abortSignal: opts.abortSignal });
|
|
63
|
+
return monitorWebhook({ cfg, feishuCfg: feishuCfg!, runtime: opts.runtime, abortSignal: opts.abortSignal, accountId });
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
async function monitorWebSocket(params: {
|
|
@@ -61,17 +68,19 @@ async function monitorWebSocket(params: {
|
|
|
61
68
|
feishuCfg: FeishuConfig;
|
|
62
69
|
runtime?: RuntimeEnv;
|
|
63
70
|
abortSignal?: AbortSignal;
|
|
71
|
+
accountId: string;
|
|
64
72
|
}): Promise<void> {
|
|
65
|
-
const { cfg, feishuCfg, runtime, abortSignal } = params;
|
|
73
|
+
const { cfg, feishuCfg, runtime, abortSignal, accountId } = params;
|
|
66
74
|
const log = runtime?.log ?? console.log;
|
|
67
75
|
const error = runtime?.error ?? console.error;
|
|
68
76
|
|
|
69
|
-
log(
|
|
77
|
+
log(`feishu[${accountId}]: starting WebSocket connection...`);
|
|
70
78
|
|
|
71
79
|
const wsClient = createFeishuWSClient(feishuCfg);
|
|
72
|
-
|
|
80
|
+
wsClients.set(accountId, wsClient);
|
|
73
81
|
|
|
74
82
|
const chatHistories = new Map<string, HistoryEntry[]>();
|
|
83
|
+
const botOpenId = botOpenIds.get(accountId);
|
|
75
84
|
|
|
76
85
|
const eventDispatcher = createEventDispatcher(feishuCfg);
|
|
77
86
|
|
|
@@ -85,9 +94,10 @@ async function monitorWebSocket(params: {
|
|
|
85
94
|
botOpenId,
|
|
86
95
|
runtime,
|
|
87
96
|
chatHistories,
|
|
97
|
+
accountId,
|
|
88
98
|
});
|
|
89
99
|
} catch (err) {
|
|
90
|
-
error(`feishu: error handling message event: ${String(err)}`);
|
|
100
|
+
error(`feishu[${accountId}]: error handling message event: ${String(err)}`);
|
|
91
101
|
}
|
|
92
102
|
},
|
|
93
103
|
"im.message.message_read_v1": async () => {
|
|
@@ -96,30 +106,30 @@ async function monitorWebSocket(params: {
|
|
|
96
106
|
"im.chat.member.bot.added_v1": async (data) => {
|
|
97
107
|
try {
|
|
98
108
|
const event = data as unknown as FeishuBotAddedEvent;
|
|
99
|
-
log(`feishu: bot added to chat ${event.chat_id}`);
|
|
109
|
+
log(`feishu[${accountId}]: bot added to chat ${event.chat_id}`);
|
|
100
110
|
} catch (err) {
|
|
101
|
-
error(`feishu: error handling bot added event: ${String(err)}`);
|
|
111
|
+
error(`feishu[${accountId}]: error handling bot added event: ${String(err)}`);
|
|
102
112
|
}
|
|
103
113
|
},
|
|
104
114
|
"im.chat.member.bot.deleted_v1": async (data) => {
|
|
105
115
|
try {
|
|
106
116
|
const event = data as unknown as { chat_id: string };
|
|
107
|
-
log(`feishu: bot removed from chat ${event.chat_id}`);
|
|
117
|
+
log(`feishu[${accountId}]: bot removed from chat ${event.chat_id}`);
|
|
108
118
|
} catch (err) {
|
|
109
|
-
error(`feishu: error handling bot removed event: ${String(err)}`);
|
|
119
|
+
error(`feishu[${accountId}]: error handling bot removed event: ${String(err)}`);
|
|
110
120
|
}
|
|
111
121
|
},
|
|
112
122
|
});
|
|
113
123
|
|
|
114
124
|
return new Promise((resolve, reject) => {
|
|
115
125
|
const cleanup = () => {
|
|
116
|
-
if (
|
|
117
|
-
|
|
126
|
+
if (wsClients.get(accountId) === wsClient) {
|
|
127
|
+
wsClients.delete(accountId);
|
|
118
128
|
}
|
|
119
129
|
};
|
|
120
130
|
|
|
121
131
|
const handleAbort = () => {
|
|
122
|
-
log(
|
|
132
|
+
log(`feishu[${accountId}]: abort signal received, stopping WebSocket client`);
|
|
123
133
|
cleanup();
|
|
124
134
|
resolve();
|
|
125
135
|
};
|
|
@@ -137,7 +147,7 @@ async function monitorWebSocket(params: {
|
|
|
137
147
|
eventDispatcher,
|
|
138
148
|
});
|
|
139
149
|
|
|
140
|
-
log(
|
|
150
|
+
log(`feishu[${accountId}]: WebSocket client started`);
|
|
141
151
|
} catch (err) {
|
|
142
152
|
cleanup();
|
|
143
153
|
abortSignal?.removeEventListener("abort", handleAbort);
|
|
@@ -146,13 +156,22 @@ async function monitorWebSocket(params: {
|
|
|
146
156
|
});
|
|
147
157
|
}
|
|
148
158
|
|
|
149
|
-
export function stopFeishuMonitor(): void {
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
159
|
+
export function stopFeishuMonitor(accountId?: string): void {
|
|
160
|
+
if (accountId) {
|
|
161
|
+
wsClients.delete(accountId);
|
|
162
|
+
botOpenIds.delete(accountId);
|
|
163
|
+
const server = httpServers.get(accountId);
|
|
164
|
+
if (server) {
|
|
165
|
+
server.close();
|
|
166
|
+
httpServers.delete(accountId);
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
wsClients.clear();
|
|
170
|
+
botOpenIds.clear();
|
|
171
|
+
for (const server of httpServers.values()) {
|
|
172
|
+
server.close();
|
|
173
|
+
}
|
|
174
|
+
httpServers.clear();
|
|
156
175
|
}
|
|
157
176
|
}
|
|
158
177
|
|
|
@@ -161,17 +180,19 @@ async function monitorWebhook(params: {
|
|
|
161
180
|
feishuCfg: FeishuConfig;
|
|
162
181
|
runtime?: RuntimeEnv;
|
|
163
182
|
abortSignal?: AbortSignal;
|
|
183
|
+
accountId: string;
|
|
164
184
|
}): Promise<void> {
|
|
165
|
-
const { cfg, feishuCfg, runtime, abortSignal } = params;
|
|
185
|
+
const { cfg, feishuCfg, runtime, abortSignal, accountId } = params;
|
|
166
186
|
const log = runtime?.log ?? console.log;
|
|
167
187
|
const error = runtime?.error ?? console.error;
|
|
168
188
|
|
|
169
189
|
const webhookPath = feishuCfg.webhookPath ?? "/feishu/events";
|
|
170
190
|
const webhookPort = feishuCfg.webhookPort ?? 3000;
|
|
171
191
|
|
|
172
|
-
log(`feishu: starting webhook server on port ${webhookPort}, path ${webhookPath}...`);
|
|
192
|
+
log(`feishu[${accountId}]: starting webhook server on port ${webhookPort}, path ${webhookPath}...`);
|
|
173
193
|
|
|
174
194
|
const chatHistories = new Map<string, HistoryEntry[]>();
|
|
195
|
+
const botOpenId = botOpenIds.get(accountId);
|
|
175
196
|
const eventDispatcher = createEventDispatcher(feishuCfg);
|
|
176
197
|
|
|
177
198
|
eventDispatcher.register({
|
|
@@ -184,9 +205,10 @@ async function monitorWebhook(params: {
|
|
|
184
205
|
botOpenId,
|
|
185
206
|
runtime,
|
|
186
207
|
chatHistories,
|
|
208
|
+
accountId,
|
|
187
209
|
});
|
|
188
210
|
} catch (err) {
|
|
189
|
-
error(`feishu: error handling message event: ${String(err)}`);
|
|
211
|
+
error(`feishu[${accountId}]: error handling message event: ${String(err)}`);
|
|
190
212
|
}
|
|
191
213
|
},
|
|
192
214
|
"im.message.message_read_v1": async () => {
|
|
@@ -195,33 +217,33 @@ async function monitorWebhook(params: {
|
|
|
195
217
|
"im.chat.member.bot.added_v1": async (data) => {
|
|
196
218
|
try {
|
|
197
219
|
const event = data as unknown as FeishuBotAddedEvent;
|
|
198
|
-
log(`feishu: bot added to chat ${event.chat_id}`);
|
|
220
|
+
log(`feishu[${accountId}]: bot added to chat ${event.chat_id}`);
|
|
199
221
|
} catch (err) {
|
|
200
|
-
error(`feishu: error handling bot added event: ${String(err)}`);
|
|
222
|
+
error(`feishu[${accountId}]: error handling bot added event: ${String(err)}`);
|
|
201
223
|
}
|
|
202
224
|
},
|
|
203
225
|
"im.chat.member.bot.deleted_v1": async (data) => {
|
|
204
226
|
try {
|
|
205
227
|
const event = data as unknown as { chat_id: string };
|
|
206
|
-
log(`feishu: bot removed from chat ${event.chat_id}`);
|
|
228
|
+
log(`feishu[${accountId}]: bot removed from chat ${event.chat_id}`);
|
|
207
229
|
} catch (err) {
|
|
208
|
-
error(`feishu: error handling bot removed event: ${String(err)}`);
|
|
230
|
+
error(`feishu[${accountId}]: error handling bot removed event: ${String(err)}`);
|
|
209
231
|
}
|
|
210
232
|
},
|
|
211
233
|
});
|
|
212
234
|
|
|
213
235
|
return new Promise((resolve, reject) => {
|
|
214
236
|
const server = http.createServer();
|
|
215
|
-
|
|
237
|
+
httpServers.set(accountId, server);
|
|
216
238
|
|
|
217
239
|
const cleanup = () => {
|
|
218
|
-
if (
|
|
219
|
-
|
|
240
|
+
if (httpServers.get(accountId) === server) {
|
|
241
|
+
httpServers.delete(accountId);
|
|
220
242
|
}
|
|
221
243
|
};
|
|
222
244
|
|
|
223
245
|
const handleAbort = () => {
|
|
224
|
-
log(
|
|
246
|
+
log(`feishu[${accountId}]: abort signal received, stopping webhook server`);
|
|
225
247
|
server.close(() => {
|
|
226
248
|
cleanup();
|
|
227
249
|
resolve();
|
|
@@ -239,7 +261,7 @@ async function monitorWebhook(params: {
|
|
|
239
261
|
server.on("error", (err) => {
|
|
240
262
|
cleanup();
|
|
241
263
|
abortSignal?.removeEventListener("abort", handleAbort);
|
|
242
|
-
error(`feishu: webhook server error: ${String(err)}`);
|
|
264
|
+
error(`feishu[${accountId}]: webhook server error: ${String(err)}`);
|
|
243
265
|
reject(err);
|
|
244
266
|
});
|
|
245
267
|
|
|
@@ -249,7 +271,7 @@ async function monitorWebhook(params: {
|
|
|
249
271
|
}));
|
|
250
272
|
|
|
251
273
|
server.listen(webhookPort, () => {
|
|
252
|
-
log(`feishu: webhook server started on port ${webhookPort}`);
|
|
274
|
+
log(`feishu[${accountId}]: webhook server started on port ${webhookPort}`);
|
|
253
275
|
});
|
|
254
276
|
});
|
|
255
277
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { FeishuConfigSchema, FeishuGroupSchema, z } from "./config-schema.js";
|
|
1
|
+
import type { FeishuConfigSchema, FeishuGroupSchema, FeishuAccountOverrideSchema, z } from "./config-schema.js";
|
|
2
2
|
import type { MentionTarget } from "./mention.js";
|
|
3
3
|
|
|
4
4
|
export type FeishuConfig = z.infer<typeof FeishuConfigSchema>;
|
|
5
5
|
export type FeishuGroupConfig = z.infer<typeof FeishuGroupSchema>;
|
|
6
|
+
export type FeishuAccountOverride = z.infer<typeof FeishuAccountOverrideSchema>;
|
|
6
7
|
|
|
7
8
|
export type FeishuDomain = "feishu" | "lark";
|
|
8
9
|
export type FeishuConnectionMode = "websocket" | "webhook";
|