feishu-user-plugin 1.1.2 → 1.1.3
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 +1 -1
- package/CHANGELOG.md +9 -0
- package/package.json +1 -1
- package/skills/feishu-user-plugin/SKILL.md +1 -1
- package/src/index.js +23 -17
- package/src/oauth.js +5 -2
- package/src/official.js +3 -2
- package/src/test-comprehensive.js +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "feishu-user-plugin",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "All-in-one Feishu plugin for Claude Code — send messages as yourself, read chats, manage docs/tables/wiki. 33 tools + 9 skills, 3 auth layers.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "EthanQC"
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [1.1.3] - 2026-03-11
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- **Case-insensitive chat name matching**: All name resolution strategies (bot group list, im.chat.search, search_contacts) now use case-insensitive matching. "ai技术解决" now correctly matches "AI技术解决(内部)".
|
|
11
|
+
- **expires_in NaN bug**: UAT token refresh and OAuth now validate `expires_in` field, defaulting to 7200s if missing/invalid, preventing NaN corruption in config.
|
|
12
|
+
- **_populateSenderNames inefficiency**: Fixed redundant condition in cookie-based name fallback.
|
|
13
|
+
- **OAuth silent persistence failure**: Now logs warnings when token persistence to `~/.claude.json` fails, instead of silently swallowing errors.
|
|
14
|
+
- **Null safety**: Added null check in `resolveToOcId` for undefined chat_id.
|
|
15
|
+
|
|
7
16
|
## [1.1.2] - 2026-03-11
|
|
8
17
|
|
|
9
18
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "feishu-user-plugin",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "All-in-one Feishu plugin for Claude Code — send messages as yourself, read chats, manage docs/tables/wiki. 33 tools + 9 skills, 3 auth layers.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: feishu-user-plugin
|
|
3
|
-
version: "1.1.
|
|
3
|
+
version: "1.1.3"
|
|
4
4
|
description: "All-in-one Feishu plugin — send messages as yourself, read group/P2P chats, manage docs/tables/wiki. Replaces and extends the official Feishu MCP."
|
|
5
5
|
allowed-tools: send_to_user, send_to_group, send_as_user, send_image_as_user, send_file_as_user, send_post_as_user, send_sticker_as_user, send_audio_as_user, search_contacts, create_p2p_chat, get_chat_info, get_user_info, get_login_status, read_p2p_messages, list_user_chats, list_chats, read_messages, reply_message, forward_message, search_docs, read_doc, create_doc, list_bitable_tables, list_bitable_fields, search_bitable_records, create_bitable_record, update_bitable_record, list_wiki_spaces, search_wiki, list_wiki_nodes, list_files, create_folder, find_user
|
|
6
6
|
user_invocable: true
|
package/src/index.js
CHANGED
|
@@ -33,20 +33,28 @@ class ChatIdMapper {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// Case-insensitive name matching helper
|
|
37
|
+
static _nameMatch(haystack, needle, exact = false) {
|
|
38
|
+
if (!haystack || !needle) return false;
|
|
39
|
+
const h = haystack.toLowerCase(), n = needle.toLowerCase();
|
|
40
|
+
return exact ? h === n : h.includes(n);
|
|
41
|
+
}
|
|
42
|
+
|
|
36
43
|
async findByName(name, official) {
|
|
37
44
|
await this._refresh(official);
|
|
38
|
-
// Exact match first
|
|
45
|
+
// Exact match first (case-insensitive)
|
|
39
46
|
for (const [ocId, chatName] of this.nameCache) {
|
|
40
|
-
if (chatName
|
|
47
|
+
if (ChatIdMapper._nameMatch(chatName, name, true)) return ocId;
|
|
41
48
|
}
|
|
42
|
-
// Partial match
|
|
49
|
+
// Partial match (case-insensitive)
|
|
43
50
|
for (const [ocId, chatName] of this.nameCache) {
|
|
44
|
-
if (chatName
|
|
51
|
+
if (ChatIdMapper._nameMatch(chatName, name)) return ocId;
|
|
45
52
|
}
|
|
46
53
|
return null;
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
async resolveToOcId(chatIdOrName, official) {
|
|
57
|
+
if (!chatIdOrName) return null;
|
|
50
58
|
if (chatIdOrName.startsWith('oc_')) return chatIdOrName;
|
|
51
59
|
// Also accept raw numeric IDs (from search_contacts)
|
|
52
60
|
if (/^\d+$/.test(chatIdOrName)) return chatIdOrName;
|
|
@@ -58,11 +66,11 @@ class ChatIdMapper {
|
|
|
58
66
|
const results = await official.chatSearch(chatIdOrName);
|
|
59
67
|
for (const chat of results) {
|
|
60
68
|
this.nameCache.set(chat.chat_id, chat.name || '');
|
|
61
|
-
if (chat.name
|
|
69
|
+
if (ChatIdMapper._nameMatch(chat.name, chatIdOrName, true)) return chat.chat_id;
|
|
62
70
|
}
|
|
63
|
-
// Partial match on search results
|
|
71
|
+
// Partial match on search results (case-insensitive)
|
|
64
72
|
for (const chat of results) {
|
|
65
|
-
if (chat.name
|
|
73
|
+
if (ChatIdMapper._nameMatch(chat.name, chatIdOrName)) return chat.chat_id;
|
|
66
74
|
}
|
|
67
75
|
} catch (e) {
|
|
68
76
|
console.error('[feishu-user-plugin] chatSearch fallback failed:', e.message);
|
|
@@ -77,13 +85,13 @@ class ChatIdMapper {
|
|
|
77
85
|
try {
|
|
78
86
|
const results = await userClient.search(chatName);
|
|
79
87
|
const groups = results.filter(r => r.type === 'group');
|
|
80
|
-
// Exact match first
|
|
88
|
+
// Exact match first (case-insensitive)
|
|
81
89
|
for (const g of groups) {
|
|
82
|
-
if (g.title
|
|
90
|
+
if (ChatIdMapper._nameMatch(g.title, chatName, true)) return String(g.id);
|
|
83
91
|
}
|
|
84
|
-
// Partial match
|
|
92
|
+
// Partial match (case-insensitive)
|
|
85
93
|
for (const g of groups) {
|
|
86
|
-
if (g.title
|
|
94
|
+
if (ChatIdMapper._nameMatch(g.title, chatName)) return String(g.id);
|
|
87
95
|
}
|
|
88
96
|
} catch (e) {
|
|
89
97
|
console.error('[feishu-user-plugin] search_contacts fallback failed:', e.message);
|
|
@@ -531,7 +539,7 @@ const TOOLS = [
|
|
|
531
539
|
// --- Server ---
|
|
532
540
|
|
|
533
541
|
const server = new Server(
|
|
534
|
-
{ name: 'feishu-user-plugin', version: '1.1.
|
|
542
|
+
{ name: 'feishu-user-plugin', version: '1.1.3' },
|
|
535
543
|
{ capabilities: { tools: {} } }
|
|
536
544
|
);
|
|
537
545
|
|
|
@@ -663,10 +671,10 @@ async function handleTool(name, args) {
|
|
|
663
671
|
case 'read_p2p_messages': {
|
|
664
672
|
const official = getOfficialClient();
|
|
665
673
|
let chatId = args.chat_id;
|
|
674
|
+
let uc = null;
|
|
675
|
+
try { uc = await getUserClient(); } catch (_) {}
|
|
666
676
|
// If chat_id is not numeric or oc_, try to resolve as user name → P2P chat
|
|
667
677
|
if (!/^\d+$/.test(chatId) && !chatId.startsWith('oc_')) {
|
|
668
|
-
let uc = null;
|
|
669
|
-
try { uc = await getUserClient(); } catch (_) {}
|
|
670
678
|
if (uc) {
|
|
671
679
|
const results = await uc.search(chatId);
|
|
672
680
|
const user = results.find(r => r.type === 'user');
|
|
@@ -684,8 +692,6 @@ async function handleTool(name, args) {
|
|
|
684
692
|
return text(`"${args.chat_id}" is not a valid chat ID. Provide a numeric ID or oc_xxx format. Use search_contacts + create_p2p_chat to get the ID.`);
|
|
685
693
|
}
|
|
686
694
|
}
|
|
687
|
-
let uc = null;
|
|
688
|
-
try { uc = await getUserClient(); } catch (_) {}
|
|
689
695
|
return json(await official.readMessagesAsUser(chatId, {
|
|
690
696
|
pageSize: args.page_size, startTime: args.start_time, endTime: args.end_time,
|
|
691
697
|
sortType: args.sort_type,
|
|
@@ -798,7 +804,7 @@ async function main() {
|
|
|
798
804
|
const hasCookie = !!process.env.LARK_COOKIE;
|
|
799
805
|
const hasApp = !!(process.env.LARK_APP_ID && process.env.LARK_APP_SECRET);
|
|
800
806
|
const hasUAT = !!process.env.LARK_USER_ACCESS_TOKEN;
|
|
801
|
-
console.error(`[feishu-user-plugin] MCP Server v1.1.
|
|
807
|
+
console.error(`[feishu-user-plugin] MCP Server v1.1.3 — ${TOOLS.length} tools`);
|
|
802
808
|
console.error(`[feishu-user-plugin] Auth: Cookie=${hasCookie ? 'YES' : 'NO'} App=${hasApp ? 'YES' : 'NO'} UAT=${hasUAT ? 'YES' : 'NO'}`);
|
|
803
809
|
if (!hasCookie) console.error('[feishu-user-plugin] WARNING: LARK_COOKIE not set — user identity tools (send_to_user, etc.) will fail');
|
|
804
810
|
if (!hasApp) console.error('[feishu-user-plugin] WARNING: LARK_APP_ID/SECRET not set — official API tools (read_messages, docs, etc.) will fail');
|
package/src/oauth.js
CHANGED
|
@@ -108,7 +108,7 @@ function saveToken(tokenData) {
|
|
|
108
108
|
LARK_USER_ACCESS_TOKEN: tokenData.access_token,
|
|
109
109
|
LARK_USER_REFRESH_TOKEN: tokenData.refresh_token || '',
|
|
110
110
|
LARK_UAT_SCOPE: tokenData.scope || '',
|
|
111
|
-
LARK_UAT_EXPIRES: String(Math.floor(Date.now() / 1000 + tokenData.expires_in)),
|
|
111
|
+
LARK_UAT_EXPIRES: String(Math.floor(Date.now() / 1000 + (typeof tokenData.expires_in === 'number' && tokenData.expires_in > 0 ? tokenData.expires_in : 7200))),
|
|
112
112
|
};
|
|
113
113
|
|
|
114
114
|
for (const [key, val] of Object.entries(updates)) {
|
|
@@ -144,8 +144,11 @@ function _persistToClaudeJson(updates) {
|
|
|
144
144
|
return;
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
|
-
} catch {
|
|
147
|
+
} catch (e) {
|
|
148
|
+
console.error(`[feishu-user-plugin] Failed to persist tokens to ${cjPath}: ${e.message}`);
|
|
149
|
+
}
|
|
148
150
|
}
|
|
151
|
+
console.error('[feishu-user-plugin] WARNING: Could not persist tokens to ~/.claude.json. Tokens saved to .env only — copy them to your MCP config manually.');
|
|
149
152
|
}
|
|
150
153
|
|
|
151
154
|
const server = http.createServer(async (req, res) => {
|
package/src/official.js
CHANGED
|
@@ -58,7 +58,8 @@ class LarkOfficialClient {
|
|
|
58
58
|
|
|
59
59
|
this._uat = tokenData.access_token;
|
|
60
60
|
this._uatRefresh = tokenData.refresh_token || this._uatRefresh;
|
|
61
|
-
|
|
61
|
+
const expiresIn = typeof tokenData.expires_in === 'number' && tokenData.expires_in > 0 ? tokenData.expires_in : 7200;
|
|
62
|
+
this._uatExpires = Math.floor(Date.now() / 1000) + expiresIn;
|
|
62
63
|
this._persistUAT();
|
|
63
64
|
console.error('[feishu-user-plugin] UAT refreshed successfully');
|
|
64
65
|
return this._uat;
|
|
@@ -448,7 +449,7 @@ class LarkOfficialClient {
|
|
|
448
449
|
// Fallback: resolve remaining unknowns via cookie-based user identity client
|
|
449
450
|
if (userClient) {
|
|
450
451
|
for (const id of unknownIds) {
|
|
451
|
-
if (!this._userNameCache.has(id)
|
|
452
|
+
if (!this._userNameCache.has(id)) {
|
|
452
453
|
try {
|
|
453
454
|
const name = await userClient.getUserName(id);
|
|
454
455
|
if (name) this._userNameCache.set(id, name);
|
|
@@ -274,7 +274,7 @@ async function testUAT() {
|
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
async function main() {
|
|
277
|
-
console.log('=== feishu-user-plugin v1.1.
|
|
277
|
+
console.log('=== feishu-user-plugin v1.1.3 — Comprehensive Test ===\n');
|
|
278
278
|
|
|
279
279
|
await testUserIdentity();
|
|
280
280
|
console.log('');
|