@openclaw-plugins/feishu-plus 0.1.7-fork.1
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 +560 -0
- package/index.ts +74 -0
- package/openclaw.plugin.json +10 -0
- package/package.json +65 -0
- package/skills/feishu-doc/SKILL.md +99 -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-wiki/SKILL.md +96 -0
- package/src/accounts.ts +140 -0
- package/src/bitable.ts +441 -0
- package/src/bot.ts +919 -0
- package/src/channel.ts +335 -0
- package/src/client.ts +114 -0
- package/src/config-schema.ts +199 -0
- package/src/directory.ts +165 -0
- package/src/doc-schema.ts +47 -0
- package/src/docx.ts +525 -0
- package/src/drive-schema.ts +46 -0
- package/src/drive.ts +207 -0
- package/src/dynamic-agent.ts +131 -0
- package/src/media.ts +523 -0
- package/src/mention.ts +121 -0
- package/src/monitor.ts +190 -0
- package/src/onboarding.ts +358 -0
- package/src/outbound.ts +40 -0
- package/src/perm-schema.ts +52 -0
- package/src/perm.ts +166 -0
- package/src/policy.ts +92 -0
- package/src/probe.ts +115 -0
- package/src/reactions.ts +160 -0
- package/src/reply-dispatcher.ts +225 -0
- package/src/runtime.ts +14 -0
- package/src/send.ts +492 -0
- package/src/stream.ts +160 -0
- package/src/targets.ts +58 -0
- package/src/tools-config.ts +21 -0
- package/src/types.ts +77 -0
- package/src/typing.ts +75 -0
- package/src/wiki-schema.ts +55 -0
- package/src/wiki.ts +224 -0
package/src/perm.ts
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
|
+
import { createFeishuClient } from "./client.js";
|
|
3
|
+
import { listEnabledFeishuAccounts } from "./accounts.js";
|
|
4
|
+
import type * as Lark from "@larksuiteoapi/node-sdk";
|
|
5
|
+
import { FeishuPermSchema, type FeishuPermParams } from "./perm-schema.js";
|
|
6
|
+
import { resolveToolsConfig } from "./tools-config.js";
|
|
7
|
+
|
|
8
|
+
// ============ Helpers ============
|
|
9
|
+
|
|
10
|
+
function json(data: unknown) {
|
|
11
|
+
return {
|
|
12
|
+
content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
|
|
13
|
+
details: data,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type ListTokenType =
|
|
18
|
+
| "doc"
|
|
19
|
+
| "sheet"
|
|
20
|
+
| "file"
|
|
21
|
+
| "wiki"
|
|
22
|
+
| "bitable"
|
|
23
|
+
| "docx"
|
|
24
|
+
| "mindnote"
|
|
25
|
+
| "minutes"
|
|
26
|
+
| "slides";
|
|
27
|
+
type CreateTokenType =
|
|
28
|
+
| "doc"
|
|
29
|
+
| "sheet"
|
|
30
|
+
| "file"
|
|
31
|
+
| "wiki"
|
|
32
|
+
| "bitable"
|
|
33
|
+
| "docx"
|
|
34
|
+
| "folder"
|
|
35
|
+
| "mindnote"
|
|
36
|
+
| "minutes"
|
|
37
|
+
| "slides";
|
|
38
|
+
type MemberType =
|
|
39
|
+
| "email"
|
|
40
|
+
| "openid"
|
|
41
|
+
| "unionid"
|
|
42
|
+
| "openchat"
|
|
43
|
+
| "opendepartmentid"
|
|
44
|
+
| "userid"
|
|
45
|
+
| "groupid"
|
|
46
|
+
| "wikispaceid";
|
|
47
|
+
type PermType = "view" | "edit" | "full_access";
|
|
48
|
+
|
|
49
|
+
// ============ Actions ============
|
|
50
|
+
|
|
51
|
+
async function listMembers(client: Lark.Client, token: string, type: string) {
|
|
52
|
+
const res = await client.drive.permissionMember.list({
|
|
53
|
+
path: { token },
|
|
54
|
+
params: { type: type as ListTokenType },
|
|
55
|
+
});
|
|
56
|
+
if (res.code !== 0) throw new Error(res.msg);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
members:
|
|
60
|
+
res.data?.items?.map((m) => ({
|
|
61
|
+
member_type: m.member_type,
|
|
62
|
+
member_id: m.member_id,
|
|
63
|
+
perm: m.perm,
|
|
64
|
+
name: m.name,
|
|
65
|
+
})) ?? [],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function addMember(
|
|
70
|
+
client: Lark.Client,
|
|
71
|
+
token: string,
|
|
72
|
+
type: string,
|
|
73
|
+
memberType: string,
|
|
74
|
+
memberId: string,
|
|
75
|
+
perm: string,
|
|
76
|
+
) {
|
|
77
|
+
const res = await client.drive.permissionMember.create({
|
|
78
|
+
path: { token },
|
|
79
|
+
params: { type: type as CreateTokenType, need_notification: false },
|
|
80
|
+
data: {
|
|
81
|
+
member_type: memberType as MemberType,
|
|
82
|
+
member_id: memberId,
|
|
83
|
+
perm: perm as PermType,
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
if (res.code !== 0) throw new Error(res.msg);
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
success: true,
|
|
90
|
+
member: res.data?.member,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function removeMember(
|
|
95
|
+
client: Lark.Client,
|
|
96
|
+
token: string,
|
|
97
|
+
type: string,
|
|
98
|
+
memberType: string,
|
|
99
|
+
memberId: string,
|
|
100
|
+
) {
|
|
101
|
+
const res = await client.drive.permissionMember.delete({
|
|
102
|
+
path: { token, member_id: memberId },
|
|
103
|
+
params: { type: type as CreateTokenType, member_type: memberType as MemberType },
|
|
104
|
+
});
|
|
105
|
+
if (res.code !== 0) throw new Error(res.msg);
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
success: true,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ============ Tool Registration ============
|
|
113
|
+
|
|
114
|
+
export function registerFeishuPermTools(api: OpenClawPluginApi) {
|
|
115
|
+
if (!api.config) {
|
|
116
|
+
api.logger.debug?.("feishu_perm: No config available, skipping perm tools");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const accounts = listEnabledFeishuAccounts(api.config);
|
|
121
|
+
if (accounts.length === 0) {
|
|
122
|
+
api.logger.debug?.("feishu_perm: No Feishu accounts configured, skipping perm tools");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const firstAccount = accounts[0];
|
|
127
|
+
const toolsCfg = resolveToolsConfig(firstAccount.config.tools);
|
|
128
|
+
if (!toolsCfg.perm) {
|
|
129
|
+
api.logger.debug?.("feishu_perm: perm tool disabled in config (default: false)");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const getClient = () => createFeishuClient(firstAccount);
|
|
134
|
+
|
|
135
|
+
api.registerTool(
|
|
136
|
+
{
|
|
137
|
+
name: "feishu_perm",
|
|
138
|
+
label: "Feishu Perm",
|
|
139
|
+
description: "Feishu permission management. Actions: list, add, remove",
|
|
140
|
+
parameters: FeishuPermSchema,
|
|
141
|
+
async execute(_toolCallId, params) {
|
|
142
|
+
const p = params as FeishuPermParams;
|
|
143
|
+
try {
|
|
144
|
+
const client = getClient();
|
|
145
|
+
switch (p.action) {
|
|
146
|
+
case "list":
|
|
147
|
+
return json(await listMembers(client, p.token, p.type));
|
|
148
|
+
case "add":
|
|
149
|
+
return json(
|
|
150
|
+
await addMember(client, p.token, p.type, p.member_type, p.member_id, p.perm),
|
|
151
|
+
);
|
|
152
|
+
case "remove":
|
|
153
|
+
return json(await removeMember(client, p.token, p.type, p.member_type, p.member_id));
|
|
154
|
+
default:
|
|
155
|
+
return json({ error: `Unknown action: ${(p as any).action}` });
|
|
156
|
+
}
|
|
157
|
+
} catch (err) {
|
|
158
|
+
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
{ name: "feishu_perm" },
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
api.logger.info?.(`feishu_perm: Registered feishu_perm tool`);
|
|
166
|
+
}
|
package/src/policy.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { ChannelGroupContext, GroupToolPolicyConfig } from "openclaw/plugin-sdk";
|
|
2
|
+
import type { FeishuConfig, FeishuGroupConfig } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export type FeishuAllowlistMatch = {
|
|
5
|
+
allowed: boolean;
|
|
6
|
+
matchKey?: string;
|
|
7
|
+
matchSource?: "wildcard" | "id" | "name";
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function resolveFeishuAllowlistMatch(params: {
|
|
11
|
+
allowFrom: Array<string | number>;
|
|
12
|
+
senderId: string;
|
|
13
|
+
senderName?: string | null;
|
|
14
|
+
}): FeishuAllowlistMatch {
|
|
15
|
+
const allowFrom = params.allowFrom
|
|
16
|
+
.map((entry) => String(entry).trim().toLowerCase())
|
|
17
|
+
.filter(Boolean);
|
|
18
|
+
|
|
19
|
+
if (allowFrom.length === 0) return { allowed: false };
|
|
20
|
+
if (allowFrom.includes("*")) {
|
|
21
|
+
return { allowed: true, matchKey: "*", matchSource: "wildcard" };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const senderId = params.senderId.toLowerCase();
|
|
25
|
+
if (allowFrom.includes(senderId)) {
|
|
26
|
+
return { allowed: true, matchKey: senderId, matchSource: "id" };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const senderName = params.senderName?.toLowerCase();
|
|
30
|
+
if (senderName && allowFrom.includes(senderName)) {
|
|
31
|
+
return { allowed: true, matchKey: senderName, matchSource: "name" };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return { allowed: false };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function resolveFeishuGroupConfig(params: {
|
|
38
|
+
cfg?: FeishuConfig;
|
|
39
|
+
groupId?: string | null;
|
|
40
|
+
}): FeishuGroupConfig | undefined {
|
|
41
|
+
const groups = params.cfg?.groups ?? {};
|
|
42
|
+
const groupId = params.groupId?.trim();
|
|
43
|
+
if (!groupId) return undefined;
|
|
44
|
+
|
|
45
|
+
const direct = groups[groupId] as FeishuGroupConfig | undefined;
|
|
46
|
+
if (direct) return direct;
|
|
47
|
+
|
|
48
|
+
const lowered = groupId.toLowerCase();
|
|
49
|
+
const matchKey = Object.keys(groups).find((key) => key.toLowerCase() === lowered);
|
|
50
|
+
return matchKey ? (groups[matchKey] as FeishuGroupConfig | undefined) : undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function resolveFeishuGroupToolPolicy(
|
|
54
|
+
params: ChannelGroupContext,
|
|
55
|
+
): GroupToolPolicyConfig | undefined {
|
|
56
|
+
const cfg = params.cfg.channels?.feishu as FeishuConfig | undefined;
|
|
57
|
+
if (!cfg) return undefined;
|
|
58
|
+
|
|
59
|
+
const groupConfig = resolveFeishuGroupConfig({
|
|
60
|
+
cfg,
|
|
61
|
+
groupId: params.groupId,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return groupConfig?.tools;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function isFeishuGroupAllowed(params: {
|
|
68
|
+
groupPolicy: "open" | "allowlist" | "disabled";
|
|
69
|
+
allowFrom: Array<string | number>;
|
|
70
|
+
senderId: string;
|
|
71
|
+
senderName?: string | null;
|
|
72
|
+
}): boolean {
|
|
73
|
+
const { groupPolicy } = params;
|
|
74
|
+
if (groupPolicy === "disabled") return false;
|
|
75
|
+
if (groupPolicy === "open") return true;
|
|
76
|
+
return resolveFeishuAllowlistMatch(params).allowed;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function resolveFeishuReplyPolicy(params: {
|
|
80
|
+
isDirectMessage: boolean;
|
|
81
|
+
globalConfig?: FeishuConfig;
|
|
82
|
+
groupConfig?: FeishuGroupConfig;
|
|
83
|
+
}): { requireMention: boolean } {
|
|
84
|
+
if (params.isDirectMessage) {
|
|
85
|
+
return { requireMention: false };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const requireMention =
|
|
89
|
+
params.groupConfig?.requireMention ?? params.globalConfig?.requireMention ?? true;
|
|
90
|
+
|
|
91
|
+
return { requireMention };
|
|
92
|
+
}
|
package/src/probe.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { FeishuProbeResult } from "./types.js";
|
|
2
|
+
import { createFeishuClient, type FeishuClientCredentials } from "./client.js";
|
|
3
|
+
|
|
4
|
+
// Cache configuration
|
|
5
|
+
const DEFAULT_CACHE_TTL_MINUTES = 15;
|
|
6
|
+
const ERROR_CACHE_TTL_MINUTES = 5;
|
|
7
|
+
|
|
8
|
+
// Cache entry type
|
|
9
|
+
interface CacheEntry {
|
|
10
|
+
result: FeishuProbeResult;
|
|
11
|
+
timestamp: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// In-memory cache for probe results
|
|
15
|
+
const probeCache = new Map<string, CacheEntry>();
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get cache TTL in milliseconds
|
|
19
|
+
*/
|
|
20
|
+
function getCacheTtlMs(): number {
|
|
21
|
+
const envTtl = process.env.FEISHU_PROBE_CACHE_TTL_MINUTES;
|
|
22
|
+
const minutes = envTtl ? parseInt(envTtl, 10) : DEFAULT_CACHE_TTL_MINUTES;
|
|
23
|
+
return (isNaN(minutes) ? DEFAULT_CACHE_TTL_MINUTES : minutes) * 60 * 1000;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get cache key for credentials
|
|
28
|
+
*/
|
|
29
|
+
function getCacheKey(creds: FeishuClientCredentials): string {
|
|
30
|
+
return `${creds.appId}:${creds.appSecret}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Clear probe cache for a specific account or all accounts
|
|
35
|
+
* @param accountId - Optional account ID to clear. If not provided, clears all cache.
|
|
36
|
+
*/
|
|
37
|
+
export function clearProbeCache(accountId?: string): void {
|
|
38
|
+
if (accountId) {
|
|
39
|
+
// Clear cache entries matching the account ID prefix
|
|
40
|
+
for (const [key, entry] of probeCache.entries()) {
|
|
41
|
+
if (key.startsWith(`${accountId}:`)) {
|
|
42
|
+
probeCache.delete(key);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
probeCache.clear();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function probeFeishu(creds?: FeishuClientCredentials): Promise<FeishuProbeResult> {
|
|
51
|
+
if (!creds?.appId || !creds?.appSecret) {
|
|
52
|
+
return {
|
|
53
|
+
ok: false,
|
|
54
|
+
error: "missing credentials (appId, appSecret)",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check cache
|
|
59
|
+
const cacheKey = getCacheKey(creds);
|
|
60
|
+
const cached = probeCache.get(cacheKey);
|
|
61
|
+
const now = Date.now();
|
|
62
|
+
const cacheTtlMs = getCacheTtlMs();
|
|
63
|
+
|
|
64
|
+
if (cached) {
|
|
65
|
+
const isSuccess = cached.result.ok;
|
|
66
|
+
const effectiveTtl = isSuccess ? cacheTtlMs : ERROR_CACHE_TTL_MINUTES * 60 * 1000;
|
|
67
|
+
|
|
68
|
+
if (now - cached.timestamp < effectiveTtl) {
|
|
69
|
+
return cached.result;
|
|
70
|
+
}
|
|
71
|
+
// Cache expired, remove it
|
|
72
|
+
probeCache.delete(cacheKey);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const client = createFeishuClient(creds);
|
|
77
|
+
// Use bot/v3/info API to get bot information
|
|
78
|
+
const response = await (client as any).request({
|
|
79
|
+
method: "GET",
|
|
80
|
+
url: "/open-apis/bot/v3/info",
|
|
81
|
+
data: {},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (response.code !== 0) {
|
|
85
|
+
const result: FeishuProbeResult = {
|
|
86
|
+
ok: false,
|
|
87
|
+
appId: creds.appId,
|
|
88
|
+
error: `API error: ${response.msg || `code ${response.code}`}`,
|
|
89
|
+
};
|
|
90
|
+
// Cache error result with shorter TTL
|
|
91
|
+
probeCache.set(cacheKey, { result, timestamp: now });
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const bot = response.bot || response.data?.bot;
|
|
96
|
+
const result: FeishuProbeResult = {
|
|
97
|
+
ok: true,
|
|
98
|
+
appId: creds.appId,
|
|
99
|
+
botName: bot?.bot_name,
|
|
100
|
+
botOpenId: bot?.open_id,
|
|
101
|
+
};
|
|
102
|
+
// Cache successful result
|
|
103
|
+
probeCache.set(cacheKey, { result, timestamp: now });
|
|
104
|
+
return result;
|
|
105
|
+
} catch (err) {
|
|
106
|
+
const result: FeishuProbeResult = {
|
|
107
|
+
ok: false,
|
|
108
|
+
appId: creds.appId,
|
|
109
|
+
error: err instanceof Error ? err.message : String(err),
|
|
110
|
+
};
|
|
111
|
+
// Cache error result with shorter TTL
|
|
112
|
+
probeCache.set(cacheKey, { result, timestamp: now });
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
}
|
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];
|