@bobotu/feishu-fork 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/LICENSE +21 -0
- package/README.md +922 -0
- package/index.ts +65 -0
- package/openclaw.plugin.json +10 -0
- package/package.json +72 -0
- package/skills/feishu-doc/SKILL.md +161 -0
- package/skills/feishu-doc/references/block-types.md +102 -0
- package/skills/feishu-drive/SKILL.md +96 -0
- package/skills/feishu-perm/SKILL.md +90 -0
- package/skills/feishu-task/SKILL.md +210 -0
- package/skills/feishu-wiki/SKILL.md +96 -0
- package/src/accounts.ts +140 -0
- package/src/bitable-tools/actions.ts +199 -0
- package/src/bitable-tools/common.ts +90 -0
- package/src/bitable-tools/index.ts +1 -0
- package/src/bitable-tools/meta.ts +80 -0
- package/src/bitable-tools/register.ts +195 -0
- package/src/bitable-tools/schemas.ts +221 -0
- package/src/bot.ts +1125 -0
- package/src/channel.ts +334 -0
- package/src/client.ts +114 -0
- package/src/config-schema.ts +237 -0
- package/src/dedup.ts +54 -0
- package/src/directory.ts +165 -0
- package/src/doc-tools/actions.ts +341 -0
- package/src/doc-tools/common.ts +33 -0
- package/src/doc-tools/index.ts +2 -0
- package/src/doc-tools/register.ts +90 -0
- package/src/doc-tools/schemas.ts +85 -0
- package/src/doc-write-service.ts +711 -0
- package/src/drive-tools/actions.ts +182 -0
- package/src/drive-tools/common.ts +18 -0
- package/src/drive-tools/index.ts +2 -0
- package/src/drive-tools/register.ts +71 -0
- package/src/drive-tools/schemas.ts +67 -0
- package/src/dynamic-agent.ts +135 -0
- package/src/external-keys.ts +19 -0
- package/src/media.ts +510 -0
- package/src/mention.ts +121 -0
- package/src/monitor.ts +323 -0
- package/src/onboarding.ts +449 -0
- package/src/outbound.ts +40 -0
- package/src/perm-tools/actions.ts +111 -0
- package/src/perm-tools/common.ts +18 -0
- package/src/perm-tools/index.ts +2 -0
- package/src/perm-tools/register.ts +65 -0
- package/src/perm-tools/schemas.ts +52 -0
- package/src/policy.ts +117 -0
- package/src/probe.ts +147 -0
- package/src/reactions.ts +160 -0
- package/src/reply-dispatcher.ts +240 -0
- package/src/runtime.ts +14 -0
- package/src/send.ts +391 -0
- package/src/streaming-card.ts +211 -0
- package/src/targets.ts +58 -0
- package/src/task-tools/actions.ts +590 -0
- package/src/task-tools/common.ts +18 -0
- package/src/task-tools/constants.ts +13 -0
- package/src/task-tools/index.ts +1 -0
- package/src/task-tools/register.ts +263 -0
- package/src/task-tools/schemas.ts +567 -0
- package/src/text/markdown-links.ts +104 -0
- package/src/tools-common/feishu-api.ts +184 -0
- package/src/tools-common/tool-context.ts +23 -0
- package/src/tools-common/tool-exec.ts +73 -0
- package/src/tools-config.ts +22 -0
- package/src/types.ts +79 -0
- package/src/typing.ts +75 -0
- package/src/wiki-tools/actions.ts +166 -0
- package/src/wiki-tools/common.ts +18 -0
- package/src/wiki-tools/index.ts +2 -0
- package/src/wiki-tools/register.ts +66 -0
- package/src/wiki-tools/schemas.ts +55 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Type, type Static } from "@sinclair/typebox";
|
|
2
|
+
|
|
3
|
+
const TokenType = Type.Union([
|
|
4
|
+
Type.Literal("doc"),
|
|
5
|
+
Type.Literal("docx"),
|
|
6
|
+
Type.Literal("sheet"),
|
|
7
|
+
Type.Literal("bitable"),
|
|
8
|
+
Type.Literal("folder"),
|
|
9
|
+
Type.Literal("file"),
|
|
10
|
+
Type.Literal("wiki"),
|
|
11
|
+
Type.Literal("mindnote"),
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
const MemberType = Type.Union([
|
|
15
|
+
Type.Literal("email"),
|
|
16
|
+
Type.Literal("openid"),
|
|
17
|
+
Type.Literal("userid"),
|
|
18
|
+
Type.Literal("unionid"),
|
|
19
|
+
Type.Literal("openchat"),
|
|
20
|
+
Type.Literal("opendepartmentid"),
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
const Permission = Type.Union([
|
|
24
|
+
Type.Literal("view"),
|
|
25
|
+
Type.Literal("edit"),
|
|
26
|
+
Type.Literal("full_access"),
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
export const FeishuPermSchema = Type.Union([
|
|
30
|
+
Type.Object({
|
|
31
|
+
action: Type.Literal("list"),
|
|
32
|
+
token: Type.String({ description: "File token" }),
|
|
33
|
+
type: TokenType,
|
|
34
|
+
}),
|
|
35
|
+
Type.Object({
|
|
36
|
+
action: Type.Literal("add"),
|
|
37
|
+
token: Type.String({ description: "File token" }),
|
|
38
|
+
type: TokenType,
|
|
39
|
+
member_type: MemberType,
|
|
40
|
+
member_id: Type.String({ description: "Member ID (email, open_id, user_id, etc.)" }),
|
|
41
|
+
perm: Permission,
|
|
42
|
+
}),
|
|
43
|
+
Type.Object({
|
|
44
|
+
action: Type.Literal("remove"),
|
|
45
|
+
token: Type.String({ description: "File token" }),
|
|
46
|
+
type: TokenType,
|
|
47
|
+
member_type: MemberType,
|
|
48
|
+
member_id: Type.String({ description: "Member ID to remove" }),
|
|
49
|
+
}),
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
export type FeishuPermParams = Static<typeof FeishuPermSchema>;
|
package/src/policy.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { ChannelGroupContext, GroupToolPolicyConfig } from "openclaw/plugin-sdk";
|
|
2
|
+
import type { FeishuConfig, FeishuGroupConfig } from "./types.js";
|
|
3
|
+
import { normalizeFeishuTarget } from "./targets.js";
|
|
4
|
+
|
|
5
|
+
export type FeishuGroupCommandMentionBypass = "never" | "single_bot" | "always";
|
|
6
|
+
|
|
7
|
+
export type FeishuAllowlistMatch = {
|
|
8
|
+
allowed: boolean;
|
|
9
|
+
matchKey?: string;
|
|
10
|
+
matchSource?: "wildcard" | "id";
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function normalizeFeishuAllowEntry(raw: string): string {
|
|
14
|
+
const trimmed = raw.trim();
|
|
15
|
+
if (!trimmed) return "";
|
|
16
|
+
if (trimmed === "*") return "*";
|
|
17
|
+
const withoutProviderPrefix = trimmed.replace(/^feishu:/i, "");
|
|
18
|
+
const normalized = normalizeFeishuTarget(withoutProviderPrefix) ?? withoutProviderPrefix;
|
|
19
|
+
return normalized.trim().toLowerCase();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function resolveFeishuAllowlistMatch(params: {
|
|
23
|
+
allowFrom: Array<string | number>;
|
|
24
|
+
senderId: string;
|
|
25
|
+
senderIds?: Array<string | null | undefined>;
|
|
26
|
+
senderName?: string | null;
|
|
27
|
+
}): FeishuAllowlistMatch {
|
|
28
|
+
const allowFrom = params.allowFrom
|
|
29
|
+
.map((entry) => normalizeFeishuAllowEntry(String(entry)))
|
|
30
|
+
.filter(Boolean);
|
|
31
|
+
|
|
32
|
+
if (allowFrom.length === 0) return { allowed: false };
|
|
33
|
+
if (allowFrom.includes("*")) {
|
|
34
|
+
return { allowed: true, matchKey: "*", matchSource: "wildcard" };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const senderCandidates = [params.senderId, ...(params.senderIds ?? [])]
|
|
38
|
+
.map((id) => id?.trim().toLowerCase())
|
|
39
|
+
.filter((id): id is string => Boolean(id));
|
|
40
|
+
|
|
41
|
+
for (const senderId of senderCandidates) {
|
|
42
|
+
if (allowFrom.includes(senderId)) {
|
|
43
|
+
return { allowed: true, matchKey: senderId, matchSource: "id" };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { allowed: false };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function resolveFeishuGroupConfig(params: {
|
|
51
|
+
cfg?: FeishuConfig;
|
|
52
|
+
groupId?: string | null;
|
|
53
|
+
}): FeishuGroupConfig | undefined {
|
|
54
|
+
const groups = params.cfg?.groups ?? {};
|
|
55
|
+
const groupId = params.groupId?.trim();
|
|
56
|
+
if (!groupId) return undefined;
|
|
57
|
+
|
|
58
|
+
const direct = groups[groupId] as FeishuGroupConfig | undefined;
|
|
59
|
+
if (direct) return direct;
|
|
60
|
+
|
|
61
|
+
const lowered = groupId.toLowerCase();
|
|
62
|
+
const matchKey = Object.keys(groups).find((key) => key.toLowerCase() === lowered);
|
|
63
|
+
return matchKey ? (groups[matchKey] as FeishuGroupConfig | undefined) : undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function resolveFeishuGroupToolPolicy(
|
|
67
|
+
params: ChannelGroupContext,
|
|
68
|
+
): GroupToolPolicyConfig | undefined {
|
|
69
|
+
const cfg = params.cfg.channels?.feishu as FeishuConfig | undefined;
|
|
70
|
+
if (!cfg) return undefined;
|
|
71
|
+
|
|
72
|
+
const groupConfig = resolveFeishuGroupConfig({
|
|
73
|
+
cfg,
|
|
74
|
+
groupId: params.groupId,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return groupConfig?.tools;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function isFeishuGroupAllowed(params: {
|
|
81
|
+
groupPolicy: "open" | "allowlist" | "disabled";
|
|
82
|
+
allowFrom: Array<string | number>;
|
|
83
|
+
senderId: string;
|
|
84
|
+
senderIds?: Array<string | null | undefined>;
|
|
85
|
+
senderName?: string | null;
|
|
86
|
+
}): boolean {
|
|
87
|
+
const { groupPolicy } = params;
|
|
88
|
+
if (groupPolicy === "disabled") return false;
|
|
89
|
+
if (groupPolicy === "open") return true;
|
|
90
|
+
return resolveFeishuAllowlistMatch(params).allowed;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function resolveFeishuReplyPolicy(params: {
|
|
94
|
+
isDirectMessage: boolean;
|
|
95
|
+
globalConfig?: FeishuConfig;
|
|
96
|
+
groupConfig?: FeishuGroupConfig;
|
|
97
|
+
}): { requireMention: boolean } {
|
|
98
|
+
if (params.isDirectMessage) {
|
|
99
|
+
return { requireMention: false };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const requireMention =
|
|
103
|
+
params.groupConfig?.requireMention ?? params.globalConfig?.requireMention ?? true;
|
|
104
|
+
|
|
105
|
+
return { requireMention };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function resolveFeishuGroupCommandMentionBypass(params: {
|
|
109
|
+
globalConfig?: FeishuConfig;
|
|
110
|
+
groupConfig?: FeishuGroupConfig;
|
|
111
|
+
}): FeishuGroupCommandMentionBypass {
|
|
112
|
+
return (
|
|
113
|
+
params.groupConfig?.groupCommandMentionBypass ??
|
|
114
|
+
params.globalConfig?.groupCommandMentionBypass ??
|
|
115
|
+
"single_bot"
|
|
116
|
+
);
|
|
117
|
+
}
|
package/src/probe.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { FeishuProbeResult } from "./types.js";
|
|
2
|
+
import { createFeishuClient, type FeishuClientCredentials } from "./client.js";
|
|
3
|
+
|
|
4
|
+
const DECIMAL_RADIX = 10;
|
|
5
|
+
const MINUTES_TO_MS = 60 * 1000;
|
|
6
|
+
const MIN_VALID_TTL_MINUTES = 0;
|
|
7
|
+
const DEFAULT_SUCCESS_CACHE_TTL_MINUTES = 15;
|
|
8
|
+
const DEFAULT_ERROR_CACHE_TTL_MINUTES = 5;
|
|
9
|
+
const FEISHU_API_SUCCESS_CODE = 0;
|
|
10
|
+
const SUCCESS_CACHE_TTL_ENV_KEY = "FEISHU_PROBE_CACHE_TTL_MINUTES";
|
|
11
|
+
const ERROR_CACHE_TTL_ENV_KEY = "FEISHU_PROBE_ERROR_CACHE_TTL_MINUTES";
|
|
12
|
+
|
|
13
|
+
// Cache for probe results to avoid API rate limits
|
|
14
|
+
// Success TTL default: 15 minutes (900000 ms)
|
|
15
|
+
// Can be customized via environment variable: FEISHU_PROBE_CACHE_TTL_MINUTES
|
|
16
|
+
// Error TTL default: 5 minutes (300000 ms)
|
|
17
|
+
// Can be customized via environment variable: FEISHU_PROBE_ERROR_CACHE_TTL_MINUTES
|
|
18
|
+
function resolveCacheTtlMs(envKey: string, defaultMinutes: number): number {
|
|
19
|
+
const envTtl = process.env[envKey];
|
|
20
|
+
if (envTtl) {
|
|
21
|
+
const minutes = parseInt(envTtl, DECIMAL_RADIX);
|
|
22
|
+
if (!isNaN(minutes) && minutes > MIN_VALID_TTL_MINUTES) {
|
|
23
|
+
return minutes * MINUTES_TO_MS;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return defaultMinutes * MINUTES_TO_MS;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const PROBE_CACHE_TTL_MS = resolveCacheTtlMs(
|
|
30
|
+
SUCCESS_CACHE_TTL_ENV_KEY,
|
|
31
|
+
DEFAULT_SUCCESS_CACHE_TTL_MINUTES,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
interface ProbeCacheEntry {
|
|
35
|
+
result: FeishuProbeResult;
|
|
36
|
+
timestamp: number;
|
|
37
|
+
ttlMs: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const probeCache = new Map<string, ProbeCacheEntry>();
|
|
41
|
+
const PROBE_ERROR_CACHE_TTL_MS = resolveCacheTtlMs(
|
|
42
|
+
ERROR_CACHE_TTL_ENV_KEY,
|
|
43
|
+
DEFAULT_ERROR_CACHE_TTL_MINUTES,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
function getCacheKey(creds: FeishuClientCredentials): string {
|
|
47
|
+
return `${creds.appId}:${creds.domain || "feishu"}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getCachedResult(creds: FeishuClientCredentials): FeishuProbeResult | null {
|
|
51
|
+
const key = getCacheKey(creds);
|
|
52
|
+
const cached = probeCache.get(key);
|
|
53
|
+
if (!cached) return null;
|
|
54
|
+
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
if (now - cached.timestamp > cached.ttlMs) {
|
|
57
|
+
// Cache expired
|
|
58
|
+
probeCache.delete(key);
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return cached.result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function setCachedResult(creds: FeishuClientCredentials, result: FeishuProbeResult): void {
|
|
66
|
+
const key = getCacheKey(creds);
|
|
67
|
+
const ttlMs = result.ok ? PROBE_CACHE_TTL_MS : PROBE_ERROR_CACHE_TTL_MS;
|
|
68
|
+
probeCache.set(key, {
|
|
69
|
+
result,
|
|
70
|
+
timestamp: Date.now(),
|
|
71
|
+
ttlMs,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Clear the probe cache for a specific account or all accounts.
|
|
77
|
+
*/
|
|
78
|
+
export function clearProbeCache(accountId?: string): void {
|
|
79
|
+
if (accountId) {
|
|
80
|
+
// Find and delete entries matching the accountId
|
|
81
|
+
for (const [key, entry] of probeCache.entries()) {
|
|
82
|
+
if (key.startsWith(`${accountId}:`)) {
|
|
83
|
+
probeCache.delete(key);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
probeCache.clear();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function probeFeishu(creds?: FeishuClientCredentials): Promise<FeishuProbeResult> {
|
|
92
|
+
if (!creds?.appId || !creds?.appSecret) {
|
|
93
|
+
return {
|
|
94
|
+
ok: false,
|
|
95
|
+
error: "missing credentials (appId, appSecret)",
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check cache first
|
|
100
|
+
const cached = getCachedResult(creds);
|
|
101
|
+
if (cached) {
|
|
102
|
+
return cached;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const client = createFeishuClient(creds);
|
|
107
|
+
// Use bot/v3/info API to get bot information
|
|
108
|
+
const response = await (client as any).request({
|
|
109
|
+
method: "GET",
|
|
110
|
+
url: "/open-apis/bot/v3/info",
|
|
111
|
+
data: {},
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (response.code !== FEISHU_API_SUCCESS_CODE) {
|
|
115
|
+
const result: FeishuProbeResult = {
|
|
116
|
+
ok: false,
|
|
117
|
+
appId: creds.appId,
|
|
118
|
+
error: `API error: ${response.msg || `code ${response.code}`}`,
|
|
119
|
+
};
|
|
120
|
+
// Cache error results with error TTL to avoid hammering the API
|
|
121
|
+
setCachedResult(creds, result);
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const bot = response.bot || response.data?.bot;
|
|
126
|
+
const result: FeishuProbeResult = {
|
|
127
|
+
ok: true,
|
|
128
|
+
appId: creds.appId,
|
|
129
|
+
botName: bot?.bot_name,
|
|
130
|
+
botOpenId: bot?.open_id,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Cache successful result
|
|
134
|
+
setCachedResult(creds, result);
|
|
135
|
+
|
|
136
|
+
return result;
|
|
137
|
+
} catch (err) {
|
|
138
|
+
const result: FeishuProbeResult = {
|
|
139
|
+
ok: false,
|
|
140
|
+
appId: creds.appId,
|
|
141
|
+
error: err instanceof Error ? err.message : String(err),
|
|
142
|
+
};
|
|
143
|
+
// Cache error results with error TTL
|
|
144
|
+
setCachedResult(creds, result);
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
}
|
package/src/reactions.ts
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
|
2
|
+
import { createFeishuClient } from "./client.js";
|
|
3
|
+
import { resolveFeishuAccount } from "./accounts.js";
|
|
4
|
+
|
|
5
|
+
export type FeishuReaction = {
|
|
6
|
+
reactionId: string;
|
|
7
|
+
emojiType: string;
|
|
8
|
+
operatorType: "app" | "user";
|
|
9
|
+
operatorId: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Add a reaction (emoji) to a message.
|
|
14
|
+
* @param emojiType - Feishu emoji type, e.g., "SMILE", "THUMBSUP", "HEART"
|
|
15
|
+
* @see https://open.feishu.cn/document/server-docs/im-v1/message-reaction/emojis-introduce
|
|
16
|
+
*/
|
|
17
|
+
export async function addReactionFeishu(params: {
|
|
18
|
+
cfg: ClawdbotConfig;
|
|
19
|
+
messageId: string;
|
|
20
|
+
emojiType: string;
|
|
21
|
+
accountId?: string;
|
|
22
|
+
}): Promise<{ reactionId: string }> {
|
|
23
|
+
const { cfg, messageId, emojiType, accountId } = params;
|
|
24
|
+
const account = resolveFeishuAccount({ cfg, accountId });
|
|
25
|
+
if (!account.configured) {
|
|
26
|
+
throw new Error(`Feishu account "${account.accountId}" not configured`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const client = createFeishuClient(account);
|
|
30
|
+
|
|
31
|
+
const response = (await client.im.messageReaction.create({
|
|
32
|
+
path: { message_id: messageId },
|
|
33
|
+
data: {
|
|
34
|
+
reaction_type: {
|
|
35
|
+
emoji_type: emojiType,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
})) as {
|
|
39
|
+
code?: number;
|
|
40
|
+
msg?: string;
|
|
41
|
+
data?: { reaction_id?: string };
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
if (response.code !== 0) {
|
|
45
|
+
throw new Error(`Feishu add reaction failed: ${response.msg || `code ${response.code}`}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const reactionId = response.data?.reaction_id;
|
|
49
|
+
if (!reactionId) {
|
|
50
|
+
throw new Error("Feishu add reaction failed: no reaction_id returned");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { reactionId };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Remove a reaction from a message.
|
|
58
|
+
*/
|
|
59
|
+
export async function removeReactionFeishu(params: {
|
|
60
|
+
cfg: ClawdbotConfig;
|
|
61
|
+
messageId: string;
|
|
62
|
+
reactionId: string;
|
|
63
|
+
accountId?: string;
|
|
64
|
+
}): Promise<void> {
|
|
65
|
+
const { cfg, messageId, reactionId, accountId } = params;
|
|
66
|
+
const account = resolveFeishuAccount({ cfg, accountId });
|
|
67
|
+
if (!account.configured) {
|
|
68
|
+
throw new Error(`Feishu account "${account.accountId}" not configured`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const client = createFeishuClient(account);
|
|
72
|
+
|
|
73
|
+
const response = (await client.im.messageReaction.delete({
|
|
74
|
+
path: {
|
|
75
|
+
message_id: messageId,
|
|
76
|
+
reaction_id: reactionId,
|
|
77
|
+
},
|
|
78
|
+
})) as { code?: number; msg?: string };
|
|
79
|
+
|
|
80
|
+
if (response.code !== 0) {
|
|
81
|
+
throw new Error(`Feishu remove reaction failed: ${response.msg || `code ${response.code}`}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* List all reactions for a message.
|
|
87
|
+
*/
|
|
88
|
+
export async function listReactionsFeishu(params: {
|
|
89
|
+
cfg: ClawdbotConfig;
|
|
90
|
+
messageId: string;
|
|
91
|
+
emojiType?: string;
|
|
92
|
+
accountId?: string;
|
|
93
|
+
}): Promise<FeishuReaction[]> {
|
|
94
|
+
const { cfg, messageId, emojiType, accountId } = params;
|
|
95
|
+
const account = resolveFeishuAccount({ cfg, accountId });
|
|
96
|
+
if (!account.configured) {
|
|
97
|
+
throw new Error(`Feishu account "${account.accountId}" not configured`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const client = createFeishuClient(account);
|
|
101
|
+
|
|
102
|
+
const response = (await client.im.messageReaction.list({
|
|
103
|
+
path: { message_id: messageId },
|
|
104
|
+
params: emojiType ? { reaction_type: emojiType } : undefined,
|
|
105
|
+
})) as {
|
|
106
|
+
code?: number;
|
|
107
|
+
msg?: string;
|
|
108
|
+
data?: {
|
|
109
|
+
items?: Array<{
|
|
110
|
+
reaction_id?: string;
|
|
111
|
+
reaction_type?: { emoji_type?: string };
|
|
112
|
+
operator_type?: string;
|
|
113
|
+
operator_id?: { open_id?: string; user_id?: string; union_id?: string };
|
|
114
|
+
}>;
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
if (response.code !== 0) {
|
|
119
|
+
throw new Error(`Feishu list reactions failed: ${response.msg || `code ${response.code}`}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const items = response.data?.items ?? [];
|
|
123
|
+
return items.map((item) => ({
|
|
124
|
+
reactionId: item.reaction_id ?? "",
|
|
125
|
+
emojiType: item.reaction_type?.emoji_type ?? "",
|
|
126
|
+
operatorType: item.operator_type === "app" ? "app" : "user",
|
|
127
|
+
operatorId:
|
|
128
|
+
item.operator_id?.open_id ?? item.operator_id?.user_id ?? item.operator_id?.union_id ?? "",
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Common Feishu emoji types for convenience.
|
|
134
|
+
* @see https://open.feishu.cn/document/server-docs/im-v1/message-reaction/emojis-introduce
|
|
135
|
+
*/
|
|
136
|
+
export const FeishuEmoji = {
|
|
137
|
+
// Common reactions
|
|
138
|
+
THUMBSUP: "THUMBSUP",
|
|
139
|
+
THUMBSDOWN: "THUMBSDOWN",
|
|
140
|
+
HEART: "HEART",
|
|
141
|
+
SMILE: "SMILE",
|
|
142
|
+
GRINNING: "GRINNING",
|
|
143
|
+
LAUGHING: "LAUGHING",
|
|
144
|
+
CRY: "CRY",
|
|
145
|
+
ANGRY: "ANGRY",
|
|
146
|
+
SURPRISED: "SURPRISED",
|
|
147
|
+
THINKING: "THINKING",
|
|
148
|
+
CLAP: "CLAP",
|
|
149
|
+
OK: "OK",
|
|
150
|
+
FIST: "FIST",
|
|
151
|
+
PRAY: "PRAY",
|
|
152
|
+
FIRE: "FIRE",
|
|
153
|
+
PARTY: "PARTY",
|
|
154
|
+
CHECK: "CHECK",
|
|
155
|
+
CROSS: "CROSS",
|
|
156
|
+
QUESTION: "QUESTION",
|
|
157
|
+
EXCLAMATION: "EXCLAMATION",
|
|
158
|
+
} as const;
|
|
159
|
+
|
|
160
|
+
export type FeishuEmojiType = (typeof FeishuEmoji)[keyof typeof FeishuEmoji];
|