@superbenxxxh/feishu 1.0.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/LICENSE +21 -0
- package/README.md +337 -0
- package/index.ts +53 -0
- package/openclaw.plugin.json +9 -0
- package/package.json +60 -0
- package/src/accounts.ts +53 -0
- package/src/bot.ts +685 -0
- package/src/channel.ts +224 -0
- package/src/client.ts +66 -0
- package/src/config-schema.ts +107 -0
- package/src/directory.ts +159 -0
- package/src/media.ts +515 -0
- package/src/mention.ts +121 -0
- package/src/monitor.ts +151 -0
- package/src/onboarding.ts +358 -0
- package/src/outbound.ts +40 -0
- package/src/policy.ts +92 -0
- package/src/probe.ts +46 -0
- package/src/reactions.ts +157 -0
- package/src/reply-dispatcher.ts +166 -0
- package/src/runtime.ts +14 -0
- package/src/send.ts +325 -0
- package/src/targets.ts +58 -0
- package/src/types.ts +55 -0
- package/src/typing.ts +73 -0
package/src/targets.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { FeishuIdType } from "./types.js";
|
|
2
|
+
|
|
3
|
+
const CHAT_ID_PREFIX = "oc_";
|
|
4
|
+
const OPEN_ID_PREFIX = "ou_";
|
|
5
|
+
const USER_ID_REGEX = /^[a-zA-Z0-9_-]+$/;
|
|
6
|
+
|
|
7
|
+
export function detectIdType(id: string): FeishuIdType | null {
|
|
8
|
+
const trimmed = id.trim();
|
|
9
|
+
if (trimmed.startsWith(CHAT_ID_PREFIX)) return "chat_id";
|
|
10
|
+
if (trimmed.startsWith(OPEN_ID_PREFIX)) return "open_id";
|
|
11
|
+
if (USER_ID_REGEX.test(trimmed)) return "user_id";
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function normalizeFeishuTarget(raw: string): string | null {
|
|
16
|
+
const trimmed = raw.trim();
|
|
17
|
+
if (!trimmed) return null;
|
|
18
|
+
|
|
19
|
+
const lowered = trimmed.toLowerCase();
|
|
20
|
+
if (lowered.startsWith("chat:")) {
|
|
21
|
+
return trimmed.slice("chat:".length).trim() || null;
|
|
22
|
+
}
|
|
23
|
+
if (lowered.startsWith("user:")) {
|
|
24
|
+
return trimmed.slice("user:".length).trim() || null;
|
|
25
|
+
}
|
|
26
|
+
if (lowered.startsWith("open_id:")) {
|
|
27
|
+
return trimmed.slice("open_id:".length).trim() || null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return trimmed;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function formatFeishuTarget(id: string, type?: FeishuIdType): string {
|
|
34
|
+
const trimmed = id.trim();
|
|
35
|
+
if (type === "chat_id" || trimmed.startsWith(CHAT_ID_PREFIX)) {
|
|
36
|
+
return `chat:${trimmed}`;
|
|
37
|
+
}
|
|
38
|
+
if (type === "open_id" || trimmed.startsWith(OPEN_ID_PREFIX)) {
|
|
39
|
+
return `user:${trimmed}`;
|
|
40
|
+
}
|
|
41
|
+
return trimmed;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function resolveReceiveIdType(id: string): "chat_id" | "open_id" | "user_id" {
|
|
45
|
+
const trimmed = id.trim();
|
|
46
|
+
if (trimmed.startsWith(CHAT_ID_PREFIX)) return "chat_id";
|
|
47
|
+
if (trimmed.startsWith(OPEN_ID_PREFIX)) return "open_id";
|
|
48
|
+
return "open_id";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function looksLikeFeishuId(raw: string): boolean {
|
|
52
|
+
const trimmed = raw.trim();
|
|
53
|
+
if (!trimmed) return false;
|
|
54
|
+
if (/^(chat|user|open_id):/i.test(trimmed)) return true;
|
|
55
|
+
if (trimmed.startsWith(CHAT_ID_PREFIX)) return true;
|
|
56
|
+
if (trimmed.startsWith(OPEN_ID_PREFIX)) return true;
|
|
57
|
+
return false;
|
|
58
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { FeishuConfigSchema, FeishuGroupSchema, z } from "./config-schema.js";
|
|
2
|
+
import type { MentionTarget } from "./mention.js";
|
|
3
|
+
|
|
4
|
+
export type FeishuConfig = z.infer<typeof FeishuConfigSchema>;
|
|
5
|
+
export type FeishuGroupConfig = z.infer<typeof FeishuGroupSchema>;
|
|
6
|
+
|
|
7
|
+
export type FeishuDomain = "feishu" | "lark";
|
|
8
|
+
export type FeishuConnectionMode = "websocket" | "webhook";
|
|
9
|
+
|
|
10
|
+
export type ResolvedFeishuAccount = {
|
|
11
|
+
accountId: string;
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
configured: boolean;
|
|
14
|
+
appId?: string;
|
|
15
|
+
domain: FeishuDomain;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type FeishuIdType = "open_id" | "user_id" | "union_id" | "chat_id";
|
|
19
|
+
|
|
20
|
+
export type FeishuMessageContext = {
|
|
21
|
+
chatId: string;
|
|
22
|
+
messageId: string;
|
|
23
|
+
senderId: string;
|
|
24
|
+
senderOpenId: string;
|
|
25
|
+
senderName?: string;
|
|
26
|
+
chatType: "p2p" | "group";
|
|
27
|
+
mentionedBot: boolean;
|
|
28
|
+
rootId?: string;
|
|
29
|
+
parentId?: string;
|
|
30
|
+
content: string;
|
|
31
|
+
contentType: string;
|
|
32
|
+
/** Mention forward targets (excluding the bot itself) */
|
|
33
|
+
mentionTargets?: MentionTarget[];
|
|
34
|
+
/** Extracted message body (after removing @ placeholders) */
|
|
35
|
+
mentionMessageBody?: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type FeishuSendResult = {
|
|
39
|
+
messageId: string;
|
|
40
|
+
chatId: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type FeishuProbeResult = {
|
|
44
|
+
ok: boolean;
|
|
45
|
+
error?: string;
|
|
46
|
+
appId?: string;
|
|
47
|
+
botName?: string;
|
|
48
|
+
botOpenId?: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type FeishuMediaInfo = {
|
|
52
|
+
path: string;
|
|
53
|
+
contentType?: string;
|
|
54
|
+
placeholder: string;
|
|
55
|
+
};
|
package/src/typing.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
|
2
|
+
import type { FeishuConfig } from "./types.js";
|
|
3
|
+
import { createFeishuClient } from "./client.js";
|
|
4
|
+
|
|
5
|
+
// Feishu emoji types for typing indicator
|
|
6
|
+
// See: https://open.feishu.cn/document/server-docs/im-v1/message-reaction/emojis-introduce
|
|
7
|
+
// Full list: https://github.com/go-lark/lark/blob/main/emoji.go
|
|
8
|
+
const TYPING_EMOJI = "Typing"; // Typing indicator emoji
|
|
9
|
+
|
|
10
|
+
export type TypingIndicatorState = {
|
|
11
|
+
messageId: string;
|
|
12
|
+
reactionId: string | null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Add a typing indicator (reaction) to a message
|
|
17
|
+
*/
|
|
18
|
+
export async function addTypingIndicator(params: {
|
|
19
|
+
cfg: ClawdbotConfig;
|
|
20
|
+
messageId: string;
|
|
21
|
+
}): Promise<TypingIndicatorState> {
|
|
22
|
+
const { cfg, messageId } = params;
|
|
23
|
+
const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
|
|
24
|
+
if (!feishuCfg) {
|
|
25
|
+
return { messageId, reactionId: null };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const client = createFeishuClient(feishuCfg);
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const response = await client.im.messageReaction.create({
|
|
32
|
+
path: { message_id: messageId },
|
|
33
|
+
data: {
|
|
34
|
+
reaction_type: { emoji_type: TYPING_EMOJI },
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const reactionId = (response as any)?.data?.reaction_id ?? null;
|
|
39
|
+
return { messageId, reactionId };
|
|
40
|
+
} catch (err) {
|
|
41
|
+
// Silently fail - typing indicator is not critical
|
|
42
|
+
console.log(`[feishu] failed to add typing indicator: ${err}`);
|
|
43
|
+
return { messageId, reactionId: null };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Remove a typing indicator (reaction) from a message
|
|
49
|
+
*/
|
|
50
|
+
export async function removeTypingIndicator(params: {
|
|
51
|
+
cfg: ClawdbotConfig;
|
|
52
|
+
state: TypingIndicatorState;
|
|
53
|
+
}): Promise<void> {
|
|
54
|
+
const { cfg, state } = params;
|
|
55
|
+
if (!state.reactionId) return;
|
|
56
|
+
|
|
57
|
+
const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
|
|
58
|
+
if (!feishuCfg) return;
|
|
59
|
+
|
|
60
|
+
const client = createFeishuClient(feishuCfg);
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
await client.im.messageReaction.delete({
|
|
64
|
+
path: {
|
|
65
|
+
message_id: state.messageId,
|
|
66
|
+
reaction_id: state.reactionId,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
} catch (err) {
|
|
70
|
+
// Silently fail - cleanup is not critical
|
|
71
|
+
console.log(`[feishu] failed to remove typing indicator: ${err}`);
|
|
72
|
+
}
|
|
73
|
+
}
|