feat(feishu): replace built-in SDK with community plugin

Replace the built-in Feishu SDK with the community-maintained
clawdbot-feishu plugin by @m1heng.

Changes:
- Remove src/feishu/ directory (19 files)
- Remove src/channels/plugins/outbound/feishu.ts
- Remove src/channels/plugins/normalize/feishu.ts
- Remove src/config/types.feishu.ts
- Remove feishu exports from plugin-sdk/index.ts
- Remove FeishuConfig from types.channels.ts

New features in community plugin:
- Document tools (read/create/edit Feishu docs)
- Wiki tools (navigate/manage knowledge base)
- Drive tools (folder/file management)
- Bitable tools (read/write table records)
- Permission tools (collaborator management)
- Emoji reactions support
- Typing indicators
- Rich media support (bidirectional image/file transfer)
- @mention handling
- Skills for feishu-doc, feishu-wiki, feishu-drive, feishu-perm

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Yifeng Wang
2026-02-05 18:26:05 +08:00
committed by cpojer
parent 02842bef91
commit 2267d58afc
66 changed files with 5702 additions and 4486 deletions

View File

@@ -0,0 +1,118 @@
import type { FeishuMessageEvent } from "./bot.js";
/**
* Mention target user info
*/
export type MentionTarget = {
openId: string;
name: string;
key: string; // Placeholder in original message, e.g. @_user_1
};
/**
* Extract mention targets from message event (excluding the bot itself)
*/
export function extractMentionTargets(
event: FeishuMessageEvent,
botOpenId?: string,
): MentionTarget[] {
const mentions = event.message.mentions ?? [];
return mentions
.filter((m) => {
// Exclude the bot itself
if (botOpenId && m.id.open_id === botOpenId) return false;
// Must have open_id
return !!m.id.open_id;
})
.map((m) => ({
openId: m.id.open_id!,
name: m.name,
key: m.key,
}));
}
/**
* Check if message is a mention forward request
* Rules:
* - Group: message mentions bot + at least one other user
* - DM: message mentions any user (no need to mention bot)
*/
export function isMentionForwardRequest(event: FeishuMessageEvent, botOpenId?: string): boolean {
const mentions = event.message.mentions ?? [];
if (mentions.length === 0) return false;
const isDirectMessage = event.message.chat_type === "p2p";
const hasOtherMention = mentions.some((m) => m.id.open_id !== botOpenId);
if (isDirectMessage) {
// DM: trigger if any non-bot user is mentioned
return hasOtherMention;
} else {
// Group: need to mention both bot and other users
const hasBotMention = mentions.some((m) => m.id.open_id === botOpenId);
return hasBotMention && hasOtherMention;
}
}
/**
* Extract message body from text (remove @ placeholders)
*/
export function extractMessageBody(text: string, allMentionKeys: string[]): string {
let result = text;
// Remove all @ placeholders
for (const key of allMentionKeys) {
result = result.replace(new RegExp(key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), "");
}
return result.replace(/\s+/g, " ").trim();
}
/**
* Format @mention for text message
*/
export function formatMentionForText(target: MentionTarget): string {
return `<at user_id="${target.openId}">${target.name}</at>`;
}
/**
* Format @everyone for text message
*/
export function formatMentionAllForText(): string {
return `<at user_id="all">Everyone</at>`;
}
/**
* Format @mention for card message (lark_md)
*/
export function formatMentionForCard(target: MentionTarget): string {
return `<at id=${target.openId}></at>`;
}
/**
* Format @everyone for card message
*/
export function formatMentionAllForCard(): string {
return `<at id=all></at>`;
}
/**
* Build complete message with @mentions (text format)
*/
export function buildMentionedMessage(targets: MentionTarget[], message: string): string {
if (targets.length === 0) return message;
const mentionParts = targets.map((t) => formatMentionForText(t));
return `${mentionParts.join(" ")} ${message}`;
}
/**
* Build card content with @mentions (Markdown format)
*/
export function buildMentionedCardContent(targets: MentionTarget[], message: string): string {
if (targets.length === 0) return message;
const mentionParts = targets.map((t) => formatMentionForCard(t));
return `${mentionParts.join(" ")} ${message}`;
}