feishu-user-plugin 1.3.0 → 1.3.2
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/.claude-plugin/plugin.json +2 -2
- package/README.md +103 -39
- package/package.json +5 -3
- package/proto/lark.proto +27 -0
- package/scripts/confirm-version.js +28 -0
- package/scripts/mcp_stdio_bridge.js +97 -0
- package/skills/feishu-user-plugin/references/CLAUDE.md +105 -41
- package/src/cli.js +12 -7
- package/src/client.js +81 -10
- package/src/config.js +202 -27
- package/src/index.js +128 -246
- package/src/oauth.js +2 -1
- package/src/official.js +274 -209
- package/src/setup.js +19 -3
|
@@ -6,7 +6,7 @@ All-in-one Feishu plugin for Claude Code with three auth layers:
|
|
|
6
6
|
- **Official API** (app credentials): Read group messages, docs, tables, wiki, drive, contacts, upload files
|
|
7
7
|
- **User OAuth UAT** (user_access_token): Read P2P chat history, list all user's chats
|
|
8
8
|
|
|
9
|
-
## Tool Categories (
|
|
9
|
+
## Tool Categories (66 tools)
|
|
10
10
|
|
|
11
11
|
### User Identity — Messaging (reverse-engineered, cookie-based)
|
|
12
12
|
- `send_to_user` — Search user + send text (one step, most common). Returns candidates if multiple matches.
|
|
@@ -14,7 +14,8 @@ All-in-one Feishu plugin for Claude Code with three auth layers:
|
|
|
14
14
|
- `send_as_user` — Send text to any chat by ID, supports reply threading (root_id/parent_id)
|
|
15
15
|
- `send_image_as_user` — Send image (requires image_key from `upload_image`)
|
|
16
16
|
- `send_file_as_user` — Send file (requires file_key from `upload_file`)
|
|
17
|
-
- `send_post_as_user` — Send rich text with title + formatted paragraphs
|
|
17
|
+
- `send_post_as_user` — Send rich text with title + formatted paragraphs. Elements: `{tag:"text"}`, `{tag:"a",href,text}`, `{tag:"at",userId,name}`. **@-mentions trigger real notifications** (fixed by registering AT element IDs in RichText.atIds field 6 — reverse-engineered from Feishu Web bundle's AtProperty + RichText schemas).
|
|
18
|
+
- `send_as_user` / `send_to_user` / `send_to_group` — plain text sends now accept optional `ats: [{userId, name}]`; the text must contain the `@<name>` marker for each entry. The marker is spliced into a real AT element so the mentioned user is notified. Identity is the cookie user (not bot).
|
|
18
19
|
- `send_sticker_as_user` — Send sticker/emoji
|
|
19
20
|
- `send_audio_as_user` — Send audio message
|
|
20
21
|
|
|
@@ -25,9 +26,10 @@ All-in-one Feishu plugin for Claude Code with three auth layers:
|
|
|
25
26
|
- `get_user_info` — User display name lookup (official API first, cookie cache fallback)
|
|
26
27
|
- `get_login_status` — Check cookie, app, and UAT status
|
|
27
28
|
|
|
28
|
-
### User OAuth UAT Tools (P2P chat reading)
|
|
29
|
+
### User OAuth UAT Tools (P2P chat reading + user-identity creation)
|
|
29
30
|
- `read_p2p_messages` — Read P2P (direct message) chat history. chat_id accepts both numeric IDs (from create_p2p_chat) and oc_xxx format. Returns newest messages first by default.
|
|
30
31
|
- `list_user_chats` — List group chats the user is in. Note: API only returns groups, not P2P. For P2P, use: `search_contacts` → `create_p2p_chat` → `read_p2p_messages`.
|
|
32
|
+
- **All docx + bitable + drive create/read/write tools are UAT-first**: when UAT is configured, every operation (create/edit/delete doc blocks, bitable tables/fields/views/records, drive folders) tries the user's token first and falls back to app token on failure. This keeps resources consistently owned by the user and avoids 403 errors when the app can't access user-created resources. Read-only tools (e.g. `read_doc`, `get_doc_blocks`, `list_bitable_tables`) are also UAT-first so user-owned resources remain readable.
|
|
31
33
|
|
|
32
34
|
### Official API Tools (app credentials)
|
|
33
35
|
- `list_chats` / `read_messages` — Chat history (read_messages accepts chat name, oc_ ID, or numeric ID; auto-resolves via bot's group list → im.chat.search → search_contacts). **Auto-falls back to UAT for external groups the bot cannot access.** Returns newest messages first by default. Messages include sender names.
|
|
@@ -35,26 +37,22 @@ All-in-one Feishu plugin for Claude Code with three auth layers:
|
|
|
35
37
|
- `reply_message` / `forward_message` — Message operations (as bot)
|
|
36
38
|
- `delete_message` / `update_message` — Recall or edit bot's own messages
|
|
37
39
|
- `add_reaction` / `delete_reaction` — Emoji reactions on messages
|
|
38
|
-
- `pin_message`
|
|
40
|
+
- `pin_message` — Pin or unpin a message (pinned=true/false)
|
|
39
41
|
- `create_group` / `update_group` — Create and manage group chats
|
|
40
|
-
- `list_members` / `
|
|
42
|
+
- `list_members` / `manage_members` — Group membership (manage_members: action=add/remove)
|
|
41
43
|
- `search_docs` / `read_doc` / `get_doc_blocks` / `create_doc` — Document operations
|
|
42
44
|
- `create_doc_block` / `update_doc_block` / `delete_doc_blocks` — Document content editing (insert/update/delete blocks)
|
|
43
|
-
- `create_bitable`
|
|
44
|
-
- `list_bitable_tables` / `create_bitable_table` — Table management
|
|
45
|
+
- `create_bitable` / `get_bitable_meta` / `copy_bitable` — Bitable app management (create, get info, copy)
|
|
46
|
+
- `list_bitable_tables` / `create_bitable_table` / `update_bitable_table` / `delete_bitable_table` — Table management (CRUD + rename)
|
|
45
47
|
- `list_bitable_fields` / `create_bitable_field` / `update_bitable_field` / `delete_bitable_field` — Field (column) management
|
|
46
|
-
- `list_bitable_views` —
|
|
47
|
-
- `search_bitable_records` — Query records
|
|
48
|
-
- `
|
|
49
|
-
- `batch_create_bitable_records` / `batch_update_bitable_records` / `batch_delete_bitable_records` — Batch operations (max 500/call)
|
|
48
|
+
- `list_bitable_views` / `create_bitable_view` / `delete_bitable_view` — View management (grid, kanban, gallery, form, gantt, calendar)
|
|
49
|
+
- `search_bitable_records` / `get_bitable_record` — Query records
|
|
50
|
+
- `batch_create_bitable_records` / `batch_update_bitable_records` / `batch_delete_bitable_records` — Record CRUD (single or batch, max 500/call)
|
|
50
51
|
- `list_wiki_spaces` / `search_wiki` / `list_wiki_nodes` — Wiki
|
|
51
52
|
- `list_files` / `create_folder` — Drive
|
|
52
53
|
- `copy_file` / `move_file` / `delete_file` — Drive file operations (copy, move, delete)
|
|
53
54
|
- `upload_image` / `upload_file` — Upload image/file, returns key for send_image/send_file
|
|
54
55
|
- `find_user` — Contact lookup by email/mobile
|
|
55
|
-
- `list_calendars` / `create_calendar_event` / `list_calendar_events` / `delete_calendar_event` — Calendar management
|
|
56
|
-
- `get_freebusy` — Check user availability
|
|
57
|
-
- `create_task` / `get_task` / `list_tasks` / `update_task` / `complete_task` — Task management
|
|
58
56
|
|
|
59
57
|
## Usage Patterns
|
|
60
58
|
|
|
@@ -62,7 +60,9 @@ All-in-one Feishu plugin for Claude Code with three auth layers:
|
|
|
62
60
|
- Send text as yourself → `send_to_user` or `send_to_group`
|
|
63
61
|
- Send image → `upload_image` → `send_image_as_user`
|
|
64
62
|
- Send file → `upload_file` → `send_file_as_user`
|
|
65
|
-
- Send rich content → `send_post_as_user` (formatted text
|
|
63
|
+
- Send rich content → `send_post_as_user` (formatted text + links + real @-mentions via `{tag:"at",userId,name}`)
|
|
64
|
+
- Send text with @-mentions (plain text) → `send_as_user` / `send_to_user` / `send_to_group` with `ats:[{userId,name}]` + text containing `@<name>` markers
|
|
65
|
+
- Bot-identity @-mention alternative → `send_message_as_bot` with `<at user_id="ou_xxx">Name</at>` inline in content text
|
|
66
66
|
- Reply as user in thread → `send_as_user` with root_id
|
|
67
67
|
- Reply as bot → `reply_message` (official API)
|
|
68
68
|
|
|
@@ -73,14 +73,18 @@ All-in-one Feishu plugin for Claude Code with three auth layers:
|
|
|
73
73
|
|
|
74
74
|
### Bitable (Multi-dimensional Tables)
|
|
75
75
|
- Create a bitable from scratch → `create_bitable` → `create_bitable_table` → `create_bitable_field`
|
|
76
|
+
- Get bitable info → `get_bitable_meta`
|
|
77
|
+
- Copy a bitable → `copy_bitable` with name and optional folder
|
|
76
78
|
- Query data → `list_bitable_tables` → `list_bitable_fields` → `search_bitable_records`
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
+
- Rename table → `update_bitable_table` with new name
|
|
80
|
+
- Read single record → `get_bitable_record`
|
|
81
|
+
- Create/update/delete records → `batch_create_bitable_records` / `batch_update_bitable_records` / `batch_delete_bitable_records` (works for single or up to 500)
|
|
79
82
|
- Manage fields → `create_bitable_field` / `update_bitable_field` (requires type param) / `delete_bitable_field`
|
|
83
|
+
- Manage views → `create_bitable_view` (type: grid/kanban/gallery/form/gantt/calendar) / `delete_bitable_view`
|
|
80
84
|
|
|
81
85
|
### Group Management
|
|
82
86
|
- Create a group → `create_group` with name and optional member open_ids
|
|
83
|
-
- Add/remove members → `
|
|
87
|
+
- Add/remove members → `manage_members` with chat_id + member_ids + action (add/remove)
|
|
84
88
|
- List members → `list_members`
|
|
85
89
|
|
|
86
90
|
### Document Editing
|
|
@@ -88,15 +92,6 @@ All-in-one Feishu plugin for Claude Code with three auth layers:
|
|
|
88
92
|
- Edit existing block → `get_doc_blocks` to find block_id → `update_doc_block`
|
|
89
93
|
- Delete blocks → `delete_doc_blocks` with start/end index range
|
|
90
94
|
|
|
91
|
-
### Calendar
|
|
92
|
-
- View schedule → `list_calendars` → `list_calendar_events`
|
|
93
|
-
- Create event → `create_calendar_event` with calendar_id, summary, start/end time
|
|
94
|
-
- Check availability → `get_freebusy` with user open_ids and time range
|
|
95
|
-
|
|
96
|
-
### Tasks
|
|
97
|
-
- Create task → `create_task` with summary, optional description/due
|
|
98
|
-
- Track tasks → `list_tasks` → `update_task` / `complete_task`
|
|
99
|
-
|
|
100
95
|
### Diagnostics
|
|
101
96
|
- Diagnose issues → `get_login_status` first
|
|
102
97
|
|
|
@@ -237,10 +232,16 @@ Tell user to restart Claude Code. Only ONE restart should be needed.
|
|
|
237
232
|
|
|
238
233
|
## Troubleshooting Guide
|
|
239
234
|
|
|
235
|
+
### If MCP disconnects mid-session
|
|
236
|
+
- **Root cause** (fixed in v1.3.1): `@larksuiteoapi/node-sdk`'s `defaultLogger.error` uses `console.log` (stdout). MCP protocol uses stdout for JSON-RPC, so SDK error logs corrupt the transport and cause immediate disconnect.
|
|
237
|
+
- **Fix**: Custom logger redirects all SDK output to stderr. Already applied in `src/official.js`.
|
|
238
|
+
- If still happening: check for any `console.log` calls in server code (only `console.error` is safe in MCP servers).
|
|
239
|
+
|
|
240
240
|
### If MCP tools are not available
|
|
241
241
|
1. Check `~/.claude.json` — config must be in **top-level** `mcpServers`, not inside `projects[*]`
|
|
242
|
-
2.
|
|
243
|
-
3.
|
|
242
|
+
2. For Codex: check `~/.codex/config.toml` has `[mcp_servers.feishu-user-plugin]` section
|
|
243
|
+
3. Restart Claude Code / Codex after config changes
|
|
244
|
+
4. After restart, tools may take a few seconds to register — if first call fails with "No such tool", wait and retry once
|
|
244
245
|
|
|
245
246
|
### If cookie authentication fails
|
|
246
247
|
- `document.cookie` in browser console CANNOT access HttpOnly cookies (`session`, `sl_session`)
|
|
@@ -282,9 +283,17 @@ Tell user to restart Claude Code. Only ONE restart should be needed.
|
|
|
282
283
|
- **team-skills plugin**: Skills + CLAUDE.md only (no .mcp.json). For internal team members.
|
|
283
284
|
|
|
284
285
|
### Config management
|
|
285
|
-
- `src/config.js`: Unified config module. Discovers config in `~/.claude.json` (top-level + project-level)
|
|
286
|
-
- `setup`
|
|
287
|
-
- `persistToConfig()` finds the correct config entry and writes back (used by heartbeat + UAT refresh).
|
|
286
|
+
- `src/config.js`: Unified config module. Discovers config in `~/.claude.json` (top-level + project-level), `.mcp.json`, and `~/.codex/config.toml`.
|
|
287
|
+
- `setup` writes to `~/.claude.json` (default) or `~/.codex/config.toml` (with `--client codex`), or both (`--client both`).
|
|
288
|
+
- `persistToConfig()` finds the correct config entry and writes back atomically (used by heartbeat + UAT refresh).
|
|
289
|
+
- All config writes use atomic write (tmp file + rename) to prevent race conditions with Claude Code.
|
|
290
|
+
|
|
291
|
+
### Multi-client support
|
|
292
|
+
- **Claude Code**: JSON config in `~/.claude.json` mcpServers
|
|
293
|
+
- **Codex**: TOML config in `~/.codex/config.toml` mcp_servers
|
|
294
|
+
- Setup: `npx feishu-user-plugin setup --client codex` or `--client both`
|
|
295
|
+
- MCP server code is identical for both clients — only config format differs
|
|
296
|
+
- Codex does not support Claude Code slash commands (skills) — only MCP tools are available
|
|
288
297
|
|
|
289
298
|
## Development & Publishing
|
|
290
299
|
|
|
@@ -304,17 +313,61 @@ NPM_TOKEN is stored as a GitHub repo secret.
|
|
|
304
313
|
|
|
305
314
|
### Syncing to team-skills
|
|
306
315
|
|
|
307
|
-
|
|
316
|
+
**IMPORTANT: team-skills 仓库禁止直接推送 main。所有变更必须走 PR。**
|
|
317
|
+
|
|
318
|
+
team-skills 推送规范:
|
|
319
|
+
1. **创建 feature branch**: `git checkout -b fix/feishu-xxx` 或 `sync/feishu-v1.x.x`
|
|
320
|
+
2. **提交变更并推送 branch**: `git push -u origin <branch-name>`
|
|
321
|
+
3. **创建 PR 并设置 auto-merge**: `gh pr create --title "..." --body "..."` 然后 `gh pr merge <number> --auto --merge`
|
|
322
|
+
4. **CI 通过后自动合并**: validate workflow 检查三方版本一致性,通过即自动 merge,无需手动操作
|
|
323
|
+
5. **如 CI 失败**: 修复后 push 到同一 branch,CI 会重跑,通过后自动合并
|
|
308
324
|
|
|
325
|
+
三方版本一致性规则:
|
|
326
|
+
- `plugins/feishu-user-plugin/.claude-plugin/plugin.json` 的 `version`
|
|
327
|
+
- `plugins/feishu-user-plugin/skills/feishu-user-plugin/SKILL.md` frontmatter 的 `version`
|
|
328
|
+
- `plugins/feishu-user-plugin/README.md` 更新日志里第一个 `### vX.Y.Z` 标题
|
|
329
|
+
- 这三个版本号必须相同,否则 CI 会失败。每次 npm 发包后,team-skills 的版本号也要同步更新。
|
|
330
|
+
|
|
331
|
+
同步内容(每次发版后执行):
|
|
309
332
|
```bash
|
|
310
|
-
#
|
|
311
|
-
cp
|
|
312
|
-
cp
|
|
333
|
+
# 1. 同步 skills + plugin.json
|
|
334
|
+
cp CLAUDE.md skills/feishu-user-plugin/references/CLAUDE.md
|
|
335
|
+
cp -r skills/ /Users/abble/team-skills/plugins/feishu-user-plugin/skills/
|
|
336
|
+
cp .claude-plugin/plugin.json /Users/abble/team-skills/plugins/feishu-user-plugin/.claude-plugin/
|
|
337
|
+
# 2. 手动更新 team-skills 的 README.md(工具数、更新日志)和 SKILL.md(version + allowed-tools)
|
|
338
|
+
# 3. 走 PR 流程推送
|
|
313
339
|
# Do NOT copy .mcp.json — team-skills plugin should not have one
|
|
314
340
|
```
|
|
315
341
|
|
|
316
342
|
## Development Workflow
|
|
317
343
|
|
|
344
|
+
### Keeping all docs in sync
|
|
345
|
+
When making ANY code change (new tools, bug fixes, features), update ALL of these:
|
|
346
|
+
|
|
347
|
+
**本仓库内:**
|
|
348
|
+
- `CLAUDE.md` — tool count, tool list, usage patterns, known limitations
|
|
349
|
+
- `README.md` — tool count (badge + heading + tool table), feature highlights, OpenClaw/Claude Code config examples
|
|
350
|
+
- `ROADMAP.md` — check off completed items, add new findings
|
|
351
|
+
- `package.json` — version, description (tool count)
|
|
352
|
+
- `skills/feishu-user-plugin/references/CLAUDE.md` — always copy from root: `cp CLAUDE.md skills/feishu-user-plugin/references/CLAUDE.md`
|
|
353
|
+
- `prompts/openclaw-setup.md` — if OpenClaw 相关配置变了要更新
|
|
354
|
+
|
|
355
|
+
**team-skills 仓库 (`/Users/abble/team-skills/plugins/feishu-user-plugin/`):**
|
|
356
|
+
- `skills/` — 同步技能文件: `cp -r skills/ /Users/abble/team-skills/plugins/feishu-user-plugin/skills/`
|
|
357
|
+
- `README.md` — team-skills 有自己的 README(含团队 APP_ID/SECRET),需要同步更新:工具数量、功能列表、更新日志、安装 prompt
|
|
358
|
+
- 两个 README 都必须包含 Claude Code 安装 prompt 和 OpenClaw 安装 prompt
|
|
359
|
+
- team-skills README 的安装 prompt 包含团队共享的 APP_ID/SECRET(hardcoded),本仓库 README 用占位符
|
|
360
|
+
|
|
361
|
+
**同步命令(每次发版后执行):**
|
|
362
|
+
```bash
|
|
363
|
+
# 1. 同步 skills + plugin.json
|
|
364
|
+
cp CLAUDE.md skills/feishu-user-plugin/references/CLAUDE.md
|
|
365
|
+
cp -r skills/ /Users/abble/team-skills/plugins/feishu-user-plugin/skills/
|
|
366
|
+
cp .claude-plugin/plugin.json /Users/abble/team-skills/plugins/feishu-user-plugin/.claude-plugin/
|
|
367
|
+
# 2. 手动更新 team-skills README(工具数、功能列表、更新日志)+ SKILL.md(version + allowed-tools)
|
|
368
|
+
# 3. 走 PR 流程推送 team-skills(禁止直接推 main)
|
|
369
|
+
```
|
|
370
|
+
|
|
318
371
|
### Keeping ROADMAP.md up to date
|
|
319
372
|
- When completing a feature or fixing a bug, check the corresponding item in ROADMAP.md as `[x]` done
|
|
320
373
|
- When discovering new bugs, limitations, or feature ideas during development, add them to the appropriate section in ROADMAP.md
|
|
@@ -341,16 +394,27 @@ cp .claude-plugin/plugin.json /path/to/team-skills/plugins/feishu-user-plugin/.c
|
|
|
341
394
|
- `chore:` dependencies, CI, config changes
|
|
342
395
|
|
|
343
396
|
### Publishing
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
397
|
+
**IMPORTANT: Version number must ALWAYS be confirmed with the user before publishing.**
|
|
398
|
+
Any operation involving `npm version`, modifying `package.json` version, `git tag v*`, or `git push --tags` requires explicit user confirmation of the target version number. Do not auto-decide version numbers.
|
|
399
|
+
|
|
400
|
+
Three-layer version safety:
|
|
401
|
+
1. **Claude rule** (this section): Ask user to confirm version before any publish-related operation
|
|
402
|
+
2. **Local gate** (`prepublishOnly`): Interactive confirmation when running `npm publish` locally (skipped in CI)
|
|
403
|
+
3. **CI gate** (`.github/workflows/publish.yml`): Tag must match `package.json` version or publish fails
|
|
404
|
+
|
|
405
|
+
Steps:
|
|
406
|
+
1. Confirm target version with user
|
|
407
|
+
2. Update `version` in `package.json`
|
|
408
|
+
3. `git add <files> && git commit -m "v1.x.x: description"`
|
|
409
|
+
4. `git tag v1.x.x && git push && git push --tags`
|
|
410
|
+
5. GitHub Actions verifies tag matches package.json, then auto-publishes to npm
|
|
348
411
|
|
|
349
412
|
### Syncing to team-skills (after any CLAUDE.md or skills change)
|
|
350
413
|
1. Copy CLAUDE.md to skill reference: `cp CLAUDE.md skills/feishu-user-plugin/references/CLAUDE.md`
|
|
351
414
|
2. Sync to team-skills repo: `cp -r skills/ /Users/abble/team-skills/plugins/feishu-user-plugin/skills/`
|
|
352
415
|
3. Also sync plugin.json: `cp .claude-plugin/plugin.json /Users/abble/team-skills/plugins/feishu-user-plugin/.claude-plugin/`
|
|
353
|
-
4.
|
|
416
|
+
4. Update SKILL.md version + allowed-tools, README.md changelog + tool count
|
|
417
|
+
5. **走 PR 流程**(创建 branch → push → PR → 等 CI 通过 → merge),禁止直接推 main
|
|
354
418
|
|
|
355
419
|
### Testing a tool
|
|
356
420
|
- For Official API tools: can test directly via MCP tool call or standalone script using `readCredentials()` from `src/config.js`
|
package/src/cli.js
CHANGED
|
@@ -41,23 +41,28 @@ function printHelp() {
|
|
|
41
41
|
feishu-user-plugin — All-in-one Feishu MCP Server
|
|
42
42
|
|
|
43
43
|
Commands:
|
|
44
|
-
(default) Start MCP server (used by Claude Code)
|
|
44
|
+
(default) Start MCP server (used by Claude Code / Codex)
|
|
45
45
|
setup Interactive setup wizard — writes MCP config
|
|
46
46
|
oauth Run OAuth flow to obtain user_access_token
|
|
47
47
|
status Check authentication status
|
|
48
48
|
keepalive Refresh cookie + UAT to prevent expiration (for cron jobs)
|
|
49
49
|
help Show this help
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
Setup options:
|
|
52
|
+
--app-id <id> App ID (non-interactive mode)
|
|
53
|
+
--app-secret <s> App Secret (non-interactive mode)
|
|
54
|
+
--cookie <c> Cookie string (optional)
|
|
55
|
+
--client <target> Config target: claude (default), codex, or both
|
|
56
|
+
|
|
57
|
+
Quick Start (Claude Code):
|
|
52
58
|
1. npx feishu-user-plugin setup
|
|
53
59
|
2. Follow the prompts to configure credentials
|
|
54
60
|
3. Restart Claude Code
|
|
55
61
|
|
|
56
|
-
Quick Start (
|
|
57
|
-
1.
|
|
58
|
-
2.
|
|
59
|
-
3.
|
|
60
|
-
4. Restart Claude Code
|
|
62
|
+
Quick Start (Codex):
|
|
63
|
+
1. npx feishu-user-plugin setup --client codex
|
|
64
|
+
2. Follow the prompts to configure credentials
|
|
65
|
+
3. Restart Codex
|
|
61
66
|
|
|
62
67
|
Auto-renewal (optional):
|
|
63
68
|
Add to crontab to keep tokens alive even when Claude Code is closed:
|
package/src/client.js
CHANGED
|
@@ -200,14 +200,65 @@ class LarkUserClient {
|
|
|
200
200
|
|
|
201
201
|
// --- Send Text Message ---
|
|
202
202
|
|
|
203
|
+
// Supports inline @mentions via the `ats` param:
|
|
204
|
+
// ats: [{ userId: 'ou_xxx', name: 'Alice' }]
|
|
205
|
+
// The text should contain the mention markers (defaults to `@Alice` substrings,
|
|
206
|
+
// matched in order). If `text` already contains the @Name substrings, they're
|
|
207
|
+
// found in order and spliced into rich-text AT elements.
|
|
203
208
|
async sendMessage(chatId, text, opts = {}) {
|
|
204
|
-
const
|
|
205
|
-
|
|
209
|
+
const { ats } = opts;
|
|
210
|
+
if (!Array.isArray(ats) || ats.length === 0) {
|
|
211
|
+
// Fast path: plain text, single TEXT element.
|
|
212
|
+
const elemId = generateCid();
|
|
213
|
+
const textPropBuf = this._encode('TextProperty', { content: text });
|
|
214
|
+
return this._sendMsg(MsgType.TEXT, chatId, {
|
|
215
|
+
richText: {
|
|
216
|
+
elementIds: [elemId],
|
|
217
|
+
innerText: text,
|
|
218
|
+
elements: { dictionary: { [elemId]: { tag: 1, property: textPropBuf } } },
|
|
219
|
+
},
|
|
220
|
+
}, opts);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Build rich-text segments: split `text` by each at's display marker and
|
|
224
|
+
// weave AT elements in between text elements. Each `ats[i]` is consumed
|
|
225
|
+
// in order from the remaining text.
|
|
226
|
+
const elementIds = [];
|
|
227
|
+
const atIds = [];
|
|
228
|
+
const dictionary = {};
|
|
229
|
+
let remaining = text;
|
|
230
|
+
for (const at of ats) {
|
|
231
|
+
if (!at.userId) throw new Error('sendMessage: each at entry requires userId');
|
|
232
|
+
const display = at.marker || (at.name ? '@' + at.name : '@' + at.userId);
|
|
233
|
+
const idx = remaining.indexOf(display);
|
|
234
|
+
if (idx === -1) throw new Error(`sendMessage: marker "${display}" not found in text`);
|
|
235
|
+
const before = remaining.slice(0, idx);
|
|
236
|
+
if (before) {
|
|
237
|
+
const id = generateCid();
|
|
238
|
+
elementIds.push(id);
|
|
239
|
+
dictionary[id] = { tag: 1, property: this._encode('TextProperty', { content: before }) };
|
|
240
|
+
}
|
|
241
|
+
const atId = generateCid();
|
|
242
|
+
elementIds.push(atId);
|
|
243
|
+
atIds.push(atId);
|
|
244
|
+
dictionary[atId] = {
|
|
245
|
+
tag: 5,
|
|
246
|
+
property: this._encode('AtProperty', { userId: at.userId, content: display }),
|
|
247
|
+
};
|
|
248
|
+
remaining = remaining.slice(idx + display.length);
|
|
249
|
+
}
|
|
250
|
+
if (remaining) {
|
|
251
|
+
const id = generateCid();
|
|
252
|
+
elementIds.push(id);
|
|
253
|
+
dictionary[id] = { tag: 1, property: this._encode('TextProperty', { content: remaining }) };
|
|
254
|
+
}
|
|
255
|
+
|
|
206
256
|
return this._sendMsg(MsgType.TEXT, chatId, {
|
|
207
257
|
richText: {
|
|
208
|
-
elementIds
|
|
258
|
+
elementIds,
|
|
209
259
|
innerText: text,
|
|
210
|
-
elements: { dictionary
|
|
260
|
+
elements: { dictionary },
|
|
261
|
+
atIds,
|
|
211
262
|
},
|
|
212
263
|
}, opts);
|
|
213
264
|
}
|
|
@@ -240,26 +291,43 @@ class LarkUserClient {
|
|
|
240
291
|
|
|
241
292
|
async sendPost(chatId, title, paragraphs, opts = {}) {
|
|
242
293
|
const elementIds = [];
|
|
294
|
+
const atIds = [];
|
|
295
|
+
const anchorIds = [];
|
|
243
296
|
const dictionary = {};
|
|
297
|
+
const paraTexts = [];
|
|
244
298
|
|
|
245
299
|
for (let i = 0; i < paragraphs.length; i++) {
|
|
246
300
|
const para = paragraphs[i];
|
|
301
|
+
const paraTextParts = [];
|
|
247
302
|
for (const elem of para) {
|
|
248
303
|
const elemId = generateCid();
|
|
249
304
|
elementIds.push(elemId);
|
|
250
305
|
|
|
251
306
|
if (elem.tag === 'text') {
|
|
252
|
-
const
|
|
307
|
+
const t = elem.text || '';
|
|
308
|
+
const propBuf = this._encode('TextProperty', { content: t });
|
|
253
309
|
dictionary[elemId] = { tag: 1, property: propBuf };
|
|
310
|
+
paraTextParts.push(t);
|
|
254
311
|
} else if (elem.tag === 'at') {
|
|
255
|
-
|
|
312
|
+
if (!elem.userId) throw new Error('sendPost: {tag:"at"} requires userId');
|
|
313
|
+
const displayName = elem.name || elem.userName || elem.text || elem.userId;
|
|
314
|
+
const display = displayName.startsWith('@') ? displayName : `@${displayName}`;
|
|
315
|
+
const propBuf = this._encode('AtProperty', { userId: elem.userId, content: display });
|
|
256
316
|
dictionary[elemId] = { tag: 5, property: propBuf };
|
|
317
|
+
atIds.push(elemId);
|
|
318
|
+
paraTextParts.push(display);
|
|
257
319
|
} else if (elem.tag === 'a') {
|
|
258
|
-
|
|
259
|
-
const
|
|
320
|
+
const href = elem.href || '';
|
|
321
|
+
const label = elem.text || href;
|
|
322
|
+
const propBuf = this._encode('AnchorProperty', { href, content: label, textContent: label });
|
|
260
323
|
dictionary[elemId] = { tag: 6, property: propBuf };
|
|
324
|
+
anchorIds.push(elemId);
|
|
325
|
+
paraTextParts.push(label);
|
|
326
|
+
} else {
|
|
327
|
+
throw new Error(`sendPost: unknown element tag "${elem.tag}" (supported: text, at, a)`);
|
|
261
328
|
}
|
|
262
329
|
}
|
|
330
|
+
paraTexts.push(paraTextParts.join(''));
|
|
263
331
|
// Insert newline element between paragraphs
|
|
264
332
|
if (i < paragraphs.length - 1) {
|
|
265
333
|
const nlId = generateCid();
|
|
@@ -269,10 +337,13 @@ class LarkUserClient {
|
|
|
269
337
|
}
|
|
270
338
|
}
|
|
271
339
|
|
|
272
|
-
const innerText =
|
|
340
|
+
const innerText = paraTexts.join('\n');
|
|
341
|
+
const richText = { elementIds, innerText, elements: { dictionary } };
|
|
342
|
+
if (atIds.length > 0) richText.atIds = atIds;
|
|
343
|
+
if (anchorIds.length > 0) richText.anchorIds = anchorIds;
|
|
273
344
|
return this._sendMsg(MsgType.POST, chatId, {
|
|
274
345
|
title: title || '',
|
|
275
|
-
richText
|
|
346
|
+
richText,
|
|
276
347
|
}, opts);
|
|
277
348
|
}
|
|
278
349
|
|