@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,210 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: feishu-task
|
|
3
|
+
description: |
|
|
4
|
+
Feishu Task, tasklist, subtask, comment, and attachment management. Activate when user mentions tasks, tasklists, subtasks, task comments, task attachments, or task links.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Feishu Task Tools
|
|
8
|
+
|
|
9
|
+
Tools:
|
|
10
|
+
- `feishu_task_create`
|
|
11
|
+
- `feishu_task_subtask_create`
|
|
12
|
+
- `feishu_task_get`
|
|
13
|
+
- `feishu_task_update`
|
|
14
|
+
- `feishu_task_delete`
|
|
15
|
+
- `feishu_task_comment_create`
|
|
16
|
+
- `feishu_task_comment_list`
|
|
17
|
+
- `feishu_task_comment_get`
|
|
18
|
+
- `feishu_task_comment_update`
|
|
19
|
+
- `feishu_task_comment_delete`
|
|
20
|
+
- `feishu_task_attachment_upload`
|
|
21
|
+
- `feishu_task_attachment_list`
|
|
22
|
+
- `feishu_task_attachment_get`
|
|
23
|
+
- `feishu_task_attachment_delete`
|
|
24
|
+
- `feishu_task_add_tasklist`
|
|
25
|
+
- `feishu_task_remove_tasklist`
|
|
26
|
+
- `feishu_tasklist_create`
|
|
27
|
+
- `feishu_tasklist_get`
|
|
28
|
+
- `feishu_tasklist_list`
|
|
29
|
+
- `feishu_tasklist_update`
|
|
30
|
+
- `feishu_tasklist_delete`
|
|
31
|
+
- `feishu_tasklist_add_members`
|
|
32
|
+
- `feishu_tasklist_remove_members`
|
|
33
|
+
|
|
34
|
+
## Notes
|
|
35
|
+
|
|
36
|
+
- `task_guid` can be taken from a task URL (guid query param) or from `feishu_task_get` output.
|
|
37
|
+
- `comment_id` can be obtained from `feishu_task_comment_list` output.
|
|
38
|
+
- `attachment_guid` can be obtained from `feishu_task_attachment_list` output.
|
|
39
|
+
- `user_id_type` controls returned/accepted user identity type (`open_id`, `user_id`, `union_id`).
|
|
40
|
+
- If no assignee is specified, set the assignee to the requesting user. Avoid creating unassigned tasks because the user may not be able to view them.
|
|
41
|
+
- Task visibility: users can only view tasks when they are included as assignee.
|
|
42
|
+
- Current limitation: the bot can only create subtasks for tasks created by itself.
|
|
43
|
+
- Attachment upload supports local `file_path` and remote `file_url`. Remote URLs are fetched with runtime media safety checks and size limit (`mediaMaxMb`).
|
|
44
|
+
- Keep tasklist owner as the bot. Add users as members to avoid losing bot access.
|
|
45
|
+
- Use tasklist tools for tasklist membership changes; do not use `feishu_task_update` to move tasks between tasklists.
|
|
46
|
+
|
|
47
|
+
## Create Task
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"summary": "Quarterly review",
|
|
52
|
+
"description": "Prepare review notes",
|
|
53
|
+
"due": { "timestamp": "1735689600000", "is_all_day": true },
|
|
54
|
+
"members": [
|
|
55
|
+
{ "id": "ou_xxx", "role": "assignee", "type": "user" }
|
|
56
|
+
],
|
|
57
|
+
"user_id_type": "open_id"
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Create Subtask
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"task_guid": "e297ddff-06ca-4166-b917-4ce57cd3a7a0",
|
|
66
|
+
"summary": "Draft report outline",
|
|
67
|
+
"description": "Collect key metrics",
|
|
68
|
+
"due": { "timestamp": "1735689600000", "is_all_day": true },
|
|
69
|
+
"members": [
|
|
70
|
+
{ "id": "ou_xxx", "role": "assignee", "type": "user" }
|
|
71
|
+
],
|
|
72
|
+
"user_id_type": "open_id"
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Create Comment
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"task_guid": "e297ddff-06ca-4166-b917-4ce57cd3a7a0",
|
|
81
|
+
"content": "Looks good to me",
|
|
82
|
+
"user_id_type": "open_id"
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Upload Attachment (file_path)
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"task_guid": "e297ddff-06ca-4166-b917-4ce57cd3a7a0",
|
|
91
|
+
"file_path": "/path/to/report.pdf",
|
|
92
|
+
"user_id_type": "open_id"
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Upload Attachment (file_url)
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"task_guid": "e297ddff-06ca-4166-b917-4ce57cd3a7a0",
|
|
101
|
+
"file_url": "https://oss-example.com/bucket/report.pdf",
|
|
102
|
+
"filename": "report.pdf",
|
|
103
|
+
"user_id_type": "open_id"
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Tasklist Membership For Tasks
|
|
108
|
+
|
|
109
|
+
### Add Task to Tasklist
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"task_guid": "e297ddff-06ca-4166-b917-4ce57cd3a7a0",
|
|
114
|
+
"tasklist_guid": "cc371766-6584-cf50-a222-c22cd9055004",
|
|
115
|
+
"section_guid": "6d0f9f48-2e06-4e3d-8a0f-acde196e8c61",
|
|
116
|
+
"user_id_type": "open_id"
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Remove Task from Tasklist
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"task_guid": "e297ddff-06ca-4166-b917-4ce57cd3a7a0",
|
|
125
|
+
"tasklist_guid": "cc371766-6584-cf50-a222-c22cd9055004",
|
|
126
|
+
"user_id_type": "open_id"
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Tasklists
|
|
131
|
+
|
|
132
|
+
Tasklists support three roles: owner (read/edit/manage), editor (read/edit), viewer (read).
|
|
133
|
+
|
|
134
|
+
### Create Tasklist
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"name": "Project Alpha Tasklist",
|
|
139
|
+
"members": [
|
|
140
|
+
{ "id": "ou_xxx", "type": "user", "role": "editor" }
|
|
141
|
+
],
|
|
142
|
+
"user_id_type": "open_id"
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Get Tasklist
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"tasklist_guid": "cc371766-6584-cf50-a222-c22cd9055004",
|
|
151
|
+
"user_id_type": "open_id"
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### List Tasklists
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"page_size": 50,
|
|
160
|
+
"page_token": "aWQ9NzEwMjMzMjMxMDE=",
|
|
161
|
+
"user_id_type": "open_id"
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Update Tasklist
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"tasklist_guid": "cc371766-6584-cf50-a222-c22cd9055004",
|
|
170
|
+
"tasklist": {
|
|
171
|
+
"name": "Renamed Tasklist",
|
|
172
|
+
"owner": { "id": "ou_xxx", "type": "user", "role": "owner" }
|
|
173
|
+
},
|
|
174
|
+
"update_fields": ["name", "owner"],
|
|
175
|
+
"origin_owner_to_role": "editor",
|
|
176
|
+
"user_id_type": "open_id"
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Delete Tasklist
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"tasklist_guid": "cc371766-6584-cf50-a222-c22cd9055004"
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Add Tasklist Members
|
|
189
|
+
|
|
190
|
+
```json
|
|
191
|
+
{
|
|
192
|
+
"tasklist_guid": "cc371766-6584-cf50-a222-c22cd9055004",
|
|
193
|
+
"members": [
|
|
194
|
+
{ "id": "ou_xxx", "type": "user", "role": "editor" }
|
|
195
|
+
],
|
|
196
|
+
"user_id_type": "open_id"
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Remove Tasklist Members
|
|
201
|
+
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"tasklist_guid": "cc371766-6584-cf50-a222-c22cd9055004",
|
|
205
|
+
"members": [
|
|
206
|
+
{ "id": "ou_xxx", "type": "user", "role": "viewer" }
|
|
207
|
+
],
|
|
208
|
+
"user_id_type": "open_id"
|
|
209
|
+
}
|
|
210
|
+
```
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: feishu-wiki
|
|
3
|
+
description: |
|
|
4
|
+
Feishu knowledge base navigation. Activate when user mentions knowledge base, wiki, or wiki links.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Feishu Wiki Tool
|
|
8
|
+
|
|
9
|
+
Single tool `feishu_wiki` for knowledge base operations.
|
|
10
|
+
|
|
11
|
+
## Token Extraction
|
|
12
|
+
|
|
13
|
+
From URL `https://xxx.feishu.cn/wiki/ABC123def` → `token` = `ABC123def`
|
|
14
|
+
|
|
15
|
+
## Actions
|
|
16
|
+
|
|
17
|
+
### List Knowledge Spaces
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{ "action": "spaces" }
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Returns all accessible wiki spaces.
|
|
24
|
+
|
|
25
|
+
### List Nodes
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{ "action": "nodes", "space_id": "7xxx" }
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
With parent:
|
|
32
|
+
```json
|
|
33
|
+
{ "action": "nodes", "space_id": "7xxx", "parent_node_token": "wikcnXXX" }
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Get Node Details
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{ "action": "get", "token": "ABC123def" }
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Returns: `node_token`, `obj_token`, `obj_type`, etc. Use `obj_token` with `feishu_doc` to read/write the document.
|
|
43
|
+
|
|
44
|
+
### Create Node
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{ "action": "create", "space_id": "7xxx", "title": "New Page" }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
With type and parent:
|
|
51
|
+
```json
|
|
52
|
+
{ "action": "create", "space_id": "7xxx", "title": "Sheet", "obj_type": "sheet", "parent_node_token": "wikcnXXX" }
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`obj_type`: `docx` (default), `sheet`, `bitable`, `mindnote`, `file`, `doc`, `slides`
|
|
56
|
+
|
|
57
|
+
### Move Node
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{ "action": "move", "space_id": "7xxx", "node_token": "wikcnXXX" }
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
To different location:
|
|
64
|
+
```json
|
|
65
|
+
{ "action": "move", "space_id": "7xxx", "node_token": "wikcnXXX", "target_space_id": "7yyy", "target_parent_token": "wikcnYYY" }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Rename Node
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{ "action": "rename", "space_id": "7xxx", "node_token": "wikcnXXX", "title": "New Title" }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Wiki-Doc Workflow
|
|
75
|
+
|
|
76
|
+
To edit a wiki page:
|
|
77
|
+
|
|
78
|
+
1. Get node: `{ "action": "get", "token": "wiki_token" }` → returns `obj_token`
|
|
79
|
+
2. Read doc: `feishu_doc { "action": "read", "doc_token": "obj_token" }`
|
|
80
|
+
3. Write doc: `feishu_doc { "action": "write", "doc_token": "obj_token", "content": "..." }`
|
|
81
|
+
|
|
82
|
+
## Configuration
|
|
83
|
+
|
|
84
|
+
```yaml
|
|
85
|
+
channels:
|
|
86
|
+
feishu:
|
|
87
|
+
tools:
|
|
88
|
+
wiki: true # default: true
|
|
89
|
+
doc: true # required - wiki content uses feishu_doc
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Dependency:** This tool requires `feishu_doc` to be enabled. Wiki pages are documents - use `feishu_wiki` to navigate, then `feishu_doc` to read/edit content.
|
|
93
|
+
|
|
94
|
+
## Permissions
|
|
95
|
+
|
|
96
|
+
Required: `wiki:wiki` or `wiki:wiki:readonly`
|
package/src/accounts.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
|
2
|
+
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk";
|
|
3
|
+
import type { FeishuConfig, FeishuAccountConfig, FeishuDomain, ResolvedFeishuAccount } from "./types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* List all configured account IDs from the accounts field.
|
|
7
|
+
*/
|
|
8
|
+
function listConfiguredAccountIds(cfg: ClawdbotConfig): string[] {
|
|
9
|
+
const accounts = (cfg.channels?.feishu as FeishuConfig)?.accounts;
|
|
10
|
+
if (!accounts || typeof accounts !== "object") {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
return Object.keys(accounts).filter(Boolean);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* List all Feishu account IDs.
|
|
18
|
+
* If no accounts are configured, returns [DEFAULT_ACCOUNT_ID] for backward compatibility.
|
|
19
|
+
*/
|
|
20
|
+
export function listFeishuAccountIds(cfg: ClawdbotConfig): string[] {
|
|
21
|
+
const ids = listConfiguredAccountIds(cfg);
|
|
22
|
+
if (ids.length === 0) {
|
|
23
|
+
// Backward compatibility: no accounts configured, use default
|
|
24
|
+
return [DEFAULT_ACCOUNT_ID];
|
|
25
|
+
}
|
|
26
|
+
return [...ids].sort((a, b) => a.localeCompare(b));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the default account ID.
|
|
31
|
+
*/
|
|
32
|
+
export function resolveDefaultFeishuAccountId(cfg: ClawdbotConfig): string {
|
|
33
|
+
const ids = listFeishuAccountIds(cfg);
|
|
34
|
+
if (ids.includes(DEFAULT_ACCOUNT_ID)) {
|
|
35
|
+
return DEFAULT_ACCOUNT_ID;
|
|
36
|
+
}
|
|
37
|
+
return ids[0] ?? DEFAULT_ACCOUNT_ID;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get the raw account-specific config.
|
|
42
|
+
*/
|
|
43
|
+
function resolveAccountConfig(
|
|
44
|
+
cfg: ClawdbotConfig,
|
|
45
|
+
accountId: string,
|
|
46
|
+
): FeishuAccountConfig | undefined {
|
|
47
|
+
const accounts = (cfg.channels?.feishu as FeishuConfig)?.accounts;
|
|
48
|
+
if (!accounts || typeof accounts !== "object") {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
return accounts[accountId];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Merge top-level config with account-specific config.
|
|
56
|
+
* Account-specific fields override top-level fields.
|
|
57
|
+
*/
|
|
58
|
+
function mergeFeishuAccountConfig(
|
|
59
|
+
cfg: ClawdbotConfig,
|
|
60
|
+
accountId: string,
|
|
61
|
+
): FeishuConfig {
|
|
62
|
+
const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
|
|
63
|
+
|
|
64
|
+
// Extract base config (exclude accounts field to avoid recursion)
|
|
65
|
+
const { accounts: _ignored, ...base } = feishuCfg ?? {};
|
|
66
|
+
|
|
67
|
+
// Get account-specific overrides
|
|
68
|
+
const account = resolveAccountConfig(cfg, accountId) ?? {};
|
|
69
|
+
|
|
70
|
+
// Merge: account config overrides base config
|
|
71
|
+
return { ...base, ...account } as FeishuConfig;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Resolve Feishu credentials from a config.
|
|
76
|
+
*/
|
|
77
|
+
export function resolveFeishuCredentials(cfg?: FeishuConfig): {
|
|
78
|
+
appId: string;
|
|
79
|
+
appSecret: string;
|
|
80
|
+
encryptKey?: string;
|
|
81
|
+
verificationToken?: string;
|
|
82
|
+
domain: FeishuDomain;
|
|
83
|
+
} | null {
|
|
84
|
+
const appId = cfg?.appId?.trim();
|
|
85
|
+
const appSecret = cfg?.appSecret?.trim();
|
|
86
|
+
if (!appId || !appSecret) return null;
|
|
87
|
+
return {
|
|
88
|
+
appId,
|
|
89
|
+
appSecret,
|
|
90
|
+
encryptKey: cfg?.encryptKey?.trim() || undefined,
|
|
91
|
+
verificationToken: cfg?.verificationToken?.trim() || undefined,
|
|
92
|
+
domain: cfg?.domain ?? "feishu",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Resolve a complete Feishu account with merged config.
|
|
98
|
+
*/
|
|
99
|
+
export function resolveFeishuAccount(params: {
|
|
100
|
+
cfg: ClawdbotConfig;
|
|
101
|
+
accountId?: string | null;
|
|
102
|
+
}): ResolvedFeishuAccount {
|
|
103
|
+
const accountId = normalizeAccountId(params.accountId);
|
|
104
|
+
const feishuCfg = params.cfg.channels?.feishu as FeishuConfig | undefined;
|
|
105
|
+
|
|
106
|
+
// Base enabled state (top-level)
|
|
107
|
+
const baseEnabled = feishuCfg?.enabled !== false;
|
|
108
|
+
|
|
109
|
+
// Merge configs
|
|
110
|
+
const merged = mergeFeishuAccountConfig(params.cfg, accountId);
|
|
111
|
+
|
|
112
|
+
// Account-level enabled state
|
|
113
|
+
const accountEnabled = merged.enabled !== false;
|
|
114
|
+
const enabled = baseEnabled && accountEnabled;
|
|
115
|
+
|
|
116
|
+
// Resolve credentials from merged config
|
|
117
|
+
const creds = resolveFeishuCredentials(merged);
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
accountId,
|
|
121
|
+
enabled,
|
|
122
|
+
configured: Boolean(creds),
|
|
123
|
+
name: (merged as FeishuAccountConfig).name?.trim() || undefined,
|
|
124
|
+
appId: creds?.appId,
|
|
125
|
+
appSecret: creds?.appSecret,
|
|
126
|
+
encryptKey: creds?.encryptKey,
|
|
127
|
+
verificationToken: creds?.verificationToken,
|
|
128
|
+
domain: creds?.domain ?? "feishu",
|
|
129
|
+
config: merged,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* List all enabled and configured accounts.
|
|
135
|
+
*/
|
|
136
|
+
export function listEnabledFeishuAccounts(cfg: ClawdbotConfig): ResolvedFeishuAccount[] {
|
|
137
|
+
return listFeishuAccountIds(cfg)
|
|
138
|
+
.map((accountId) => resolveFeishuAccount({ cfg, accountId }))
|
|
139
|
+
.filter((account) => account.enabled && account.configured);
|
|
140
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BitableClient,
|
|
3
|
+
BitableFieldCreateData,
|
|
4
|
+
BitableFieldUpdateData,
|
|
5
|
+
} from "./common.js";
|
|
6
|
+
import { formatField, runBitableApiCall } from "./common.js";
|
|
7
|
+
|
|
8
|
+
// -------- Field operations --------
|
|
9
|
+
|
|
10
|
+
export async function listFields(client: BitableClient, appToken: string, tableId: string) {
|
|
11
|
+
const res = await runBitableApiCall("bitable.appTableField.list", () =>
|
|
12
|
+
client.bitable.appTableField.list({
|
|
13
|
+
path: { app_token: appToken, table_id: tableId },
|
|
14
|
+
}),
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
const fields = res.data?.items ?? [];
|
|
18
|
+
return {
|
|
19
|
+
fields: fields.map((f) => formatField(f)),
|
|
20
|
+
total: fields.length,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function createField(
|
|
25
|
+
client: BitableClient,
|
|
26
|
+
appToken: string,
|
|
27
|
+
tableId: string,
|
|
28
|
+
field: BitableFieldCreateData,
|
|
29
|
+
) {
|
|
30
|
+
const res = await runBitableApiCall("bitable.appTableField.create", () =>
|
|
31
|
+
client.bitable.appTableField.create({
|
|
32
|
+
path: { app_token: appToken, table_id: tableId },
|
|
33
|
+
data: field,
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
field: res.data?.field ? formatField(res.data.field) : undefined,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function updateField(
|
|
43
|
+
client: BitableClient,
|
|
44
|
+
appToken: string,
|
|
45
|
+
tableId: string,
|
|
46
|
+
fieldId: string,
|
|
47
|
+
field: BitableFieldUpdateData,
|
|
48
|
+
) {
|
|
49
|
+
const res = await runBitableApiCall("bitable.appTableField.update", () =>
|
|
50
|
+
client.bitable.appTableField.update({
|
|
51
|
+
path: { app_token: appToken, table_id: tableId, field_id: fieldId },
|
|
52
|
+
data: field,
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
field: res.data?.field ? formatField(res.data.field) : undefined,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function deleteField(
|
|
62
|
+
client: BitableClient,
|
|
63
|
+
appToken: string,
|
|
64
|
+
tableId: string,
|
|
65
|
+
fieldId: string,
|
|
66
|
+
) {
|
|
67
|
+
const res = await runBitableApiCall("bitable.appTableField.delete", () =>
|
|
68
|
+
client.bitable.appTableField.delete({
|
|
69
|
+
path: { app_token: appToken, table_id: tableId, field_id: fieldId },
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
success: res.data?.deleted ?? true,
|
|
75
|
+
field_id: res.data?.field_id ?? fieldId,
|
|
76
|
+
deleted: res.data?.deleted ?? true,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// -------- Record operations --------
|
|
81
|
+
|
|
82
|
+
export async function listRecords(
|
|
83
|
+
client: BitableClient,
|
|
84
|
+
appToken: string,
|
|
85
|
+
tableId: string,
|
|
86
|
+
pageSize?: number,
|
|
87
|
+
pageToken?: string,
|
|
88
|
+
) {
|
|
89
|
+
const res = await runBitableApiCall("bitable.appTableRecord.list", () =>
|
|
90
|
+
client.bitable.appTableRecord.list({
|
|
91
|
+
path: { app_token: appToken, table_id: tableId },
|
|
92
|
+
params: {
|
|
93
|
+
page_size: pageSize ?? 100,
|
|
94
|
+
...(pageToken && { page_token: pageToken }),
|
|
95
|
+
},
|
|
96
|
+
}),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
records: res.data?.items ?? [],
|
|
101
|
+
has_more: res.data?.has_more ?? false,
|
|
102
|
+
page_token: res.data?.page_token,
|
|
103
|
+
total: res.data?.total,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function getRecord(
|
|
108
|
+
client: BitableClient,
|
|
109
|
+
appToken: string,
|
|
110
|
+
tableId: string,
|
|
111
|
+
recordId: string,
|
|
112
|
+
) {
|
|
113
|
+
const res = await runBitableApiCall("bitable.appTableRecord.get", () =>
|
|
114
|
+
client.bitable.appTableRecord.get({
|
|
115
|
+
path: { app_token: appToken, table_id: tableId, record_id: recordId },
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
record: res.data?.record,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function createRecord(
|
|
125
|
+
client: BitableClient,
|
|
126
|
+
appToken: string,
|
|
127
|
+
tableId: string,
|
|
128
|
+
fields: Record<string, unknown>,
|
|
129
|
+
) {
|
|
130
|
+
const res = await runBitableApiCall("bitable.appTableRecord.create", () =>
|
|
131
|
+
client.bitable.appTableRecord.create({
|
|
132
|
+
path: { app_token: appToken, table_id: tableId },
|
|
133
|
+
data: { fields },
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
record: res.data?.record,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function updateRecord(
|
|
143
|
+
client: BitableClient,
|
|
144
|
+
appToken: string,
|
|
145
|
+
tableId: string,
|
|
146
|
+
recordId: string,
|
|
147
|
+
fields: Record<string, unknown>,
|
|
148
|
+
) {
|
|
149
|
+
const res = await runBitableApiCall("bitable.appTableRecord.update", () =>
|
|
150
|
+
client.bitable.appTableRecord.update({
|
|
151
|
+
path: { app_token: appToken, table_id: tableId, record_id: recordId },
|
|
152
|
+
data: { fields },
|
|
153
|
+
}),
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
record: res.data?.record,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function deleteRecord(
|
|
162
|
+
client: BitableClient,
|
|
163
|
+
appToken: string,
|
|
164
|
+
tableId: string,
|
|
165
|
+
recordId: string,
|
|
166
|
+
) {
|
|
167
|
+
const res = await runBitableApiCall("bitable.appTableRecord.delete", () =>
|
|
168
|
+
client.bitable.appTableRecord.delete({
|
|
169
|
+
path: { app_token: appToken, table_id: tableId, record_id: recordId },
|
|
170
|
+
}),
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
success: res.data?.deleted ?? true,
|
|
175
|
+
record_id: res.data?.record_id ?? recordId,
|
|
176
|
+
deleted: res.data?.deleted ?? true,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function batchDeleteRecords(
|
|
181
|
+
client: BitableClient,
|
|
182
|
+
appToken: string,
|
|
183
|
+
tableId: string,
|
|
184
|
+
recordIds: string[],
|
|
185
|
+
) {
|
|
186
|
+
const res = await runBitableApiCall("bitable.appTableRecord.batchDelete", () =>
|
|
187
|
+
client.bitable.appTableRecord.batchDelete({
|
|
188
|
+
path: { app_token: appToken, table_id: tableId },
|
|
189
|
+
data: { records: recordIds },
|
|
190
|
+
}),
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const results = res.data?.records ?? [];
|
|
194
|
+
return {
|
|
195
|
+
results,
|
|
196
|
+
requested: recordIds.length,
|
|
197
|
+
deleted: results.filter((r) => r.deleted).length,
|
|
198
|
+
};
|
|
199
|
+
}
|