iflow-feishu 1.0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 iFlow Feishu Bridge Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,147 @@
1
+ # iFlow Feishu
2
+
3
+ 将 [iFlow CLI](https://iflow.dev) 接入飞书机器人。
4
+
5
+ ## 功能特性
6
+
7
+ - **多模型支持** - 支持 iFlow CLI 的所有模型
8
+ - **流式回复** - 实时显示 AI 思考和回复过程
9
+ - **会话管理** - 自动保存上下文历史
10
+ - **模式切换** - 支持 default/yolo/plan/smart 模式
11
+ - **自动搜索** - 检测搜索关键词自动启用网络搜索
12
+
13
+ ## 快速开始
14
+
15
+ ### 一键启动
16
+
17
+ ```bash
18
+ iflow-feishu
19
+ ```
20
+
21
+ 启动脚本会自动完成以下操作:
22
+
23
+ 1. **检查 iFlow CLI** - 未安装时询问是否安装
24
+ 2. **安装插件依赖** - 自动安装 npm 依赖
25
+ 3. **安装 PM2** - 自动安装进程管理器
26
+ 4. **配置飞书凭证** - 首次使用时引导输入
27
+ 5. **启动服务**
28
+
29
+ ### 前置要求
30
+
31
+ - Node.js >= 16.0.0
32
+
33
+ ### 手动配置
34
+
35
+ 如需手动配置飞书凭证:
36
+
37
+ ```bash
38
+ mkdir -p ~/.feishu-config
39
+ echo '{
40
+ "appId": "your-app-id",
41
+ "appSecret": "your-app-secret"
42
+ }' > ~/.feishu-config/feishu-app.json
43
+ ```
44
+
45
+ ## 命令列表
46
+
47
+ | 命令 | 说明 |
48
+ |------|------|
49
+ | `/help` | 显示帮助信息 |
50
+ | `/clear` | 清空当前会话历史 |
51
+ | `/mode` | 查看当前模式 |
52
+ | `/mode <模式>` | 切换模式 (default/yolo/plan/smart) |
53
+ | `/status` | 查看会话状态 |
54
+
55
+ ## 项目结构
56
+
57
+ ```
58
+ iflow-feishu/
59
+ ├── src/
60
+ │ ├── index.js # 入口文件
61
+ │ ├── config/
62
+ │ │ └── config.js # 配置管理
63
+ │ ├── core/
64
+ │ │ ├── service.js # 主服务(协调者)
65
+ │ │ ├── card-builder.js # 卡片构建器
66
+ │ │ ├── constants.js # 常量定义
67
+ │ │ ├── feishu-client.js # 飞书 API 客户端
68
+ │ │ ├── http-server.js # HTTP 服务器
69
+ │ │ ├── iflow-client.js # iFlow CLI 客户端
70
+ │ │ ├── message-processor.js # 消息处理器
71
+ │ │ ├── session.js # 会话管理
72
+ │ │ ├── stream-handler.js # 流式处理
73
+ │ │ └── websocket-client.js # WebSocket 客户端
74
+ │ ├── handlers/
75
+ │ │ └── commands.js # 命令处理器
76
+ │ └── utils/
77
+ │ └── logger.js # 日志工具
78
+ ├── config/
79
+ │ └── config.example.json # 配置示例
80
+ ├── package.json
81
+ └── README.md
82
+ ```
83
+
84
+ ## 架构说明
85
+
86
+ ### 模块职责
87
+
88
+ | 模块 | 职责 |
89
+ |------|------|
90
+ | service.js | 协调各模块,管理服务生命周期 |
91
+ | message-processor.js | 消息解析、上下文构建 |
92
+ | stream-handler.js | 流式响应处理、token 提取 |
93
+ | http-server.js | HTTP 服务,健康检查,事件回调 |
94
+ | websocket-client.js | 飞书 WebSocket 连接 |
95
+ | feishu-client.js | 飞书 API 封装 |
96
+ | iflow-client.js | iFlow CLI 调用封装 |
97
+ | session.js | 会话持久化存储 |
98
+ | card-builder.js | 飞书卡片构建 |
99
+ | commands.js | 命令处理逻辑 |
100
+
101
+ ### 数据流
102
+
103
+ ```
104
+ 飞书消息 -> WebSocket -> service.handleMessageEvent()
105
+ -> messageProcessor.parseMessage()
106
+ -> commandHandler.handle() 或 processMessage()
107
+ -> iflowClient.execute() (流式)
108
+ -> streamHandler.extractResponse()
109
+ -> feishuClient.updateCardMessage()
110
+ ```
111
+
112
+ ## 配置选项
113
+
114
+ | 选项 | 说明 | 默认值 |
115
+ |------|------|--------|
116
+ | `feishu.appId` | 飞书应用 ID | - |
117
+ | `feishu.appSecret` | 飞书应用密钥 | - |
118
+ | `server.port` | HTTP 服务端口 | 18080 |
119
+ | `sessions.maxHistory` | 最大历史消息数 | 15 |
120
+ | `iflow.timeout` | CLI 超时时间(ms) | 300000 |
121
+
122
+ ## 环境变量
123
+
124
+ | 变量 | 说明 |
125
+ |------|------|
126
+ | `PORT` | 服务端口 |
127
+ | `LOG_LEVEL` | 日志级别 (DEBUG/INFO/WARN/ERROR) |
128
+
129
+ ## 常用操作
130
+
131
+ ```bash
132
+ # 查看服务状态
133
+ pm2 status iflow-feishu
134
+
135
+ # 查看日志
136
+ pm2 logs iflow-feishu
137
+
138
+ # 重启服务
139
+ pm2 restart iflow-feishu
140
+
141
+ # 健康检查
142
+ curl http://localhost:18080/health
143
+ ```
144
+
145
+ ## 许可证
146
+
147
+ MIT
package/bin/config.js ADDED
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * iFlow Feishu 配置工具
5
+ * 在终端中配置飞书凭证
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const readline = require('readline');
11
+
12
+ const CONFIG_PATH = path.join(process.cwd(), 'config', 'config.json');
13
+
14
+ function loadConfig() {
15
+ if (fs.existsSync(CONFIG_PATH)) {
16
+ try {
17
+ return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
18
+ } catch (error) {
19
+ console.error('读取配置文件失败:', error.message);
20
+ }
21
+ }
22
+ return {
23
+ feishu: {},
24
+ iflow: { command: 'iflow', timeout: 120000 },
25
+ server: { port: 18080 },
26
+ sessions: { maxHistory: 15 },
27
+ card: { titleFontSize: 'small', colors: { model: 'blue', generating: 'orange', completed: 'green' } }
28
+ };
29
+ }
30
+
31
+ function saveConfig(config) {
32
+ try {
33
+ const configDir = path.dirname(CONFIG_PATH);
34
+ if (!fs.existsSync(configDir)) {
35
+ fs.mkdirSync(configDir, { recursive: true });
36
+ }
37
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
38
+ return true;
39
+ } catch (error) {
40
+ console.error('保存配置失败:', error.message);
41
+ return false;
42
+ }
43
+ }
44
+
45
+ function showStatus(config) {
46
+ console.log('\n📋 当前配置状态\n');
47
+ console.log(`App ID: ${config.feishu?.appId ? '✅ 已配置' : '❌ 未配置'}`);
48
+ console.log(`App Secret: ${config.feishu?.appSecret ? '✅ 已配置' : '❌ 未配置'}`);
49
+ console.log(`端口: ${config.server?.port || 18080}`);
50
+ console.log('');
51
+ }
52
+
53
+ async function interactiveConfig() {
54
+ const rl = readline.createInterface({
55
+ input: process.stdin,
56
+ output: process.stdout
57
+ });
58
+
59
+ const question = (prompt) => new Promise((resolve) => {
60
+ rl.question(prompt, resolve);
61
+ });
62
+
63
+ console.log('\n🔧 iFlow Feishu 配置\n');
64
+ console.log('请输入飞书应用凭证(从飞书开放平台获取)\n');
65
+
66
+ const appId = await question('App ID: ');
67
+ const appSecret = await question('App Secret: ');
68
+
69
+ rl.close();
70
+
71
+ if (!appId || !appSecret) {
72
+ console.log('\n❌ App ID 和 App Secret 不能为空');
73
+ process.exit(1);
74
+ }
75
+
76
+ const config = loadConfig();
77
+ config.feishu = { appId, appSecret };
78
+
79
+ if (saveConfig(config)) {
80
+ console.log('\n✅ 配置已保存!');
81
+ console.log(`配置文件: ${CONFIG_PATH}\n`);
82
+ console.log('现在可以启动服务了:');
83
+ console.log(' npm start\n');
84
+ } else {
85
+ console.log('\n❌ 配置保存失败');
86
+ process.exit(1);
87
+ }
88
+ }
89
+
90
+ function showHelp() {
91
+ console.log(`
92
+ 🔧 iFlow Feishu 配置工具
93
+
94
+ 用法:
95
+ node bin/config.js [命令] [选项]
96
+
97
+ 命令:
98
+ init 交互式配置(推荐)
99
+ set-appid <id> 设置 App ID
100
+ set-secret <key> 设置 App Secret
101
+ status 查看当前配置状态
102
+ help 显示帮助
103
+
104
+ 示例:
105
+ # 交互式配置
106
+ node bin/config.js init
107
+
108
+ # 直接设置
109
+ node bin/config.js set-appid cli_abc123
110
+ node bin/config.js set-secret xxxxxxxxx
111
+
112
+ # 查看状态
113
+ node bin/config.js status
114
+ `);
115
+ }
116
+
117
+ async function main() {
118
+ const args = process.argv.slice(2);
119
+ const command = args[0] || 'init';
120
+
121
+ switch (command) {
122
+ case 'init':
123
+ await interactiveConfig();
124
+ break;
125
+
126
+ case 'set-appid':
127
+ if (!args[1]) {
128
+ console.log('❌ 请提供 App ID');
129
+ console.log('用法: node bin/config.js set-appid <your-app-id>');
130
+ process.exit(1);
131
+ }
132
+ {
133
+ const config = loadConfig();
134
+ config.feishu = config.feishu || {};
135
+ config.feishu.appId = args[1];
136
+ if (saveConfig(config)) {
137
+ console.log('✅ App ID 已设置');
138
+ }
139
+ }
140
+ break;
141
+
142
+ case 'set-secret':
143
+ if (!args[1]) {
144
+ console.log('❌ 请提供 App Secret');
145
+ console.log('用法: node bin/config.js set-secret <your-app-secret>');
146
+ process.exit(1);
147
+ }
148
+ {
149
+ const config = loadConfig();
150
+ config.feishu = config.feishu || {};
151
+ config.feishu.appSecret = args[1];
152
+ if (saveConfig(config)) {
153
+ console.log('✅ App Secret 已设置');
154
+ }
155
+ }
156
+ break;
157
+
158
+ case 'status':
159
+ showStatus(loadConfig());
160
+ break;
161
+
162
+ case 'help':
163
+ case '-h':
164
+ case '--help':
165
+ showHelp();
166
+ break;
167
+
168
+ default:
169
+ console.log(`❌ 未知命令: ${command}`);
170
+ console.log('运行 "node bin/config.js help" 查看帮助');
171
+ process.exit(1);
172
+ }
173
+ }
174
+
175
+ main().catch(console.error);
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * iFlow Feishu CLI 入口
5
+ */
6
+
7
+ const path = require('path');
8
+ const { spawn } = require('child_process');
9
+
10
+ // 启动脚本路径
11
+ const scriptPath = path.join(__dirname, '..', 'iflow-feishu.sh');
12
+
13
+ // 如果启动脚本不存在,直接启动服务
14
+ const fs = require('fs');
15
+ if (!fs.existsSync(scriptPath)) {
16
+ // 直接运行 node 服务
17
+ require('../src/index.js');
18
+ return;
19
+ }
20
+
21
+ // 运行启动脚本
22
+ const child = spawn('bash', [scriptPath], {
23
+ stdio: 'inherit',
24
+ env: process.env
25
+ });
26
+
27
+ child.on('close', (code) => {
28
+ process.exit(code || 0);
29
+ });
30
+
31
+ child.on('error', (err) => {
32
+ console.error('启动失败:', err.message);
33
+ process.exit(1);
34
+ });
@@ -0,0 +1,26 @@
1
+ {
2
+ "feishu": {
3
+ "appId": "cli_xxxxxxxxxxxxxxxx",
4
+ "appSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
5
+ },
6
+ "iflow": {
7
+ "command": "iflow",
8
+ "timeout": 120000,
9
+ "workDir": ""
10
+ },
11
+ "server": {
12
+ "port": 18080
13
+ },
14
+ "sessions": {
15
+ "dir": "",
16
+ "maxHistory": 15
17
+ },
18
+ "card": {
19
+ "titleFontSize": "small",
20
+ "colors": {
21
+ "model": "blue",
22
+ "generating": "orange",
23
+ "completed": "green"
24
+ }
25
+ }
26
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "iflow-feishu",
3
+ "version": "1.0.0",
4
+ "description": "iFlow CLI 飞书插件 - 将 iFlow AI 助手接入飞书机器人",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "iflow-feishu": "./bin/iflow-feishu.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node src/index.js",
11
+ "dev": "nodemon src/index.js",
12
+ "test": "jest",
13
+ "lint": "eslint src/"
14
+ },
15
+ "keywords": [
16
+ "iflow",
17
+ "feishu",
18
+ "lark",
19
+ "ai",
20
+ "chatbot",
21
+ "feishu-bot",
22
+ "lark-bot"
23
+ ],
24
+ "author": "ai520510xyf-del <ai520510xyf@163.com>",
25
+ "license": "MIT",
26
+ "dependencies": {
27
+ "@larksuiteoapi/node-sdk": "^1.x"
28
+ },
29
+ "devDependencies": {
30
+ "jest": "^29.x",
31
+ "nodemon": "^3.x",
32
+ "eslint": "^8.x"
33
+ },
34
+ "engines": {
35
+ "node": ">=16.0.0"
36
+ },
37
+ "files": [
38
+ "src/",
39
+ "bin/",
40
+ "config/config.example.json",
41
+ "README.md",
42
+ "LICENSE"
43
+ ],
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/ai520510xyf-del/iflow-feishu.git"
47
+ },
48
+ "bugs": {
49
+ "url": "https://github.com/ai520510xyf-del/iflow-feishu/issues"
50
+ },
51
+ "homepage": "https://github.com/ai520510xyf-del/iflow-feishu#readme"
52
+ }
@@ -0,0 +1,103 @@
1
+ /**
2
+ * 配置管理模块
3
+ *
4
+ * 负责加载和验证配置
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const { DEFAULT_MODEL, DEFAULT_MAX_TOKENS, MODEL_MAX_TOKENS } = require('../core/constants');
10
+
11
+ /**
12
+ * 加载飞书配置
13
+ * @returns {Object|null}
14
+ */
15
+ function loadFeishuConfig() {
16
+ const configPath = path.join(process.env.HOME, '.feishu-config', 'feishu-app.json');
17
+
18
+ try {
19
+ if (fs.existsSync(configPath)) {
20
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
21
+ return {
22
+ appId: config.appId || config.app_id,
23
+ appSecret: config.appSecret || config.app_secret
24
+ };
25
+ }
26
+ } catch (err) {
27
+ console.error(`[ERROR] 读取飞书配置失败: ${err.message}`);
28
+ }
29
+
30
+ return null;
31
+ }
32
+
33
+ /**
34
+ * 验证配置
35
+ * @param {Object} config - 配置对象
36
+ * @throws {Error} - 配置无效时抛出错误
37
+ */
38
+ function validateConfig(config) {
39
+ if (!config.feishu?.appId) {
40
+ throw new Error('缺少飞书 App ID');
41
+ }
42
+
43
+ if (!config.feishu?.appSecret) {
44
+ throw new Error('缺少飞书 App Secret');
45
+ }
46
+
47
+ if (!config.server?.port || config.server.port < 1 || config.server.port > 65535) {
48
+ throw new Error('无效的服务端口');
49
+ }
50
+
51
+ return true;
52
+ }
53
+
54
+ // 加载飞书配置
55
+ const feishuConfig = loadFeishuConfig();
56
+
57
+ if (!feishuConfig) {
58
+ console.error('\n错误: 未找到飞书配置文件');
59
+ console.error('\n请先运行: iflow-feishu');
60
+ console.error('首次运行时会引导你配置飞书机器人凭证\n');
61
+ process.exit(1);
62
+ }
63
+
64
+ // 构建完整配置
65
+ const config = {
66
+ feishu: {
67
+ appId: feishuConfig.appId,
68
+ appSecret: feishuConfig.appSecret,
69
+ },
70
+ iflow: {
71
+ command: 'iflow',
72
+ timeout: 300000,
73
+ workDir: process.env.HOME || '/data/data/com.termux/files/home',
74
+ maxTokens: DEFAULT_MAX_TOKENS,
75
+ modelMaxTokens: MODEL_MAX_TOKENS
76
+ },
77
+ server: {
78
+ port: parseInt(process.env.PORT, 10) || 18080,
79
+ host: '0.0.0.0'
80
+ },
81
+ sessions: {
82
+ dir: path.join(process.env.HOME || '/tmp', '.iflow-feishu', 'sessions'),
83
+ maxHistory: 15,
84
+ },
85
+ card: {
86
+ titleFontSize: 'small',
87
+ colors: {
88
+ model: 'blue',
89
+ generating: 'orange',
90
+ completed: 'green'
91
+ }
92
+ }
93
+ };
94
+
95
+ // 验证配置
96
+ validateConfig(config);
97
+
98
+ // 确保会话目录存在
99
+ if (!fs.existsSync(config.sessions.dir)) {
100
+ fs.mkdirSync(config.sessions.dir, { recursive: true });
101
+ }
102
+
103
+ module.exports = config;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * 卡片构建器
3
+ */
4
+
5
+ class CardBuilder {
6
+ buildMarkdownCard(text) {
7
+ // 预处理 Markdown 内容,确保列表格式正确
8
+ const processedText = this.preprocessMarkdown(text);
9
+ return {
10
+ config: { wide_screen_mode: true },
11
+ elements: [{ tag: 'markdown', content: processedText }],
12
+ };
13
+ }
14
+
15
+ buildReasoningCard(reasoning, content, thinkingTime = null, responseTime = null,
16
+ isThinking = false, isGenerating = false, modelName = null, contentLeftPercent = null) {
17
+ const elements = [];
18
+
19
+ // 思考模块
20
+ if (reasoning && reasoning.trim()) {
21
+ let thinkingStatus = '';
22
+ if (isThinking) {
23
+ const timeStr = thinkingTime !== null ? `(${(thinkingTime/1000).toFixed(1)}s)` : '';
24
+ thinkingStatus = `💭 思考中 ${timeStr}`;
25
+ } else if (thinkingTime !== null) {
26
+ const timeStr = thinkingTime > 1000 ? `${(thinkingTime/1000).toFixed(1)}s` : `${thinkingTime}ms`;
27
+ thinkingStatus = `💭 思考完成 (${timeStr})`;
28
+ }
29
+
30
+ let titleContent = '';
31
+ if (modelName) titleContent += `<font color='blue'>${modelName}</font>`;
32
+ // 始终显示剩余上下文,默认为100%
33
+ // const displayPercent = contentLeftPercent !== null ? contentLeftPercent : 100;
34
+ // titleContent += (titleContent ? ' | ' : '') + `<font color='grey'>${displayPercent}% left</font>`;
35
+ if (thinkingStatus) titleContent += (titleContent ? ' <font color=\'grey\'>|</font> ' : '') + thinkingStatus;
36
+
37
+ if (titleContent) {
38
+ elements.push({
39
+ tag: 'div',
40
+ text: { content: titleContent, tag: 'lark_md', text_size: 'small' }
41
+ });
42
+ }
43
+
44
+ // 预处理 reasoning 内容
45
+ const processedReasoning = this.preprocessMarkdown(reasoning.trim());
46
+ elements.push({ tag: 'markdown', content: processedReasoning });
47
+ elements.push({ tag: 'hr' });
48
+ }
49
+
50
+ // 回复模块
51
+ if (content !== null || responseTime !== null || isGenerating) {
52
+ let responseTitle = '📝 回复';
53
+ if (isGenerating && responseTime !== null) {
54
+ const timeStr = responseTime > 1000 ? `${(responseTime/1000).toFixed(1)}s` : `${responseTime}ms`;
55
+ responseTitle = `📝 Doing (${timeStr})`;
56
+ } else if (!isGenerating && responseTime !== null) {
57
+ const timeStr = responseTime > 1000 ? `${(responseTime/1000).toFixed(1)}s` : `${responseTime}ms`;
58
+ responseTitle = `📝 Done (${timeStr})`;
59
+ }
60
+
61
+ let titleContent = '';
62
+ if (modelName) titleContent += `<font color='blue'>${modelName}</font>`;
63
+ // 始终显示剩余上下文,默认为100%
64
+ // const displayPercent = contentLeftPercent !== null ? contentLeftPercent : 100;
65
+ // titleContent += (titleContent ? ' | ' : '') + `<font color='grey'>${displayPercent}% left</font>`;
66
+ const statusColor = isGenerating ? 'orange' : 'green';
67
+ titleContent += (titleContent ? ' <font color=\'grey\'>|</font> ' : '') + `<font color='${statusColor}'>${responseTitle}</font>`;
68
+
69
+ if (titleContent) {
70
+ elements.push({
71
+ tag: 'div',
72
+ text: { content: titleContent, tag: 'lark_md', text_size: 'small' }
73
+ });
74
+ }
75
+
76
+ if (content && content.trim()) {
77
+ // 预处理内容,特别是列表格式
78
+ const processedContent = this.preprocessMarkdown(content.trim());
79
+ elements.push({ tag: 'markdown', content: processedContent });
80
+ }
81
+ }
82
+
83
+ return {
84
+ config: { wide_screen_mode: true },
85
+ elements: elements.length > 0 ? elements : [{ tag: 'markdown', content: '(空响应)' }]
86
+ };
87
+ }
88
+
89
+ /**
90
+ * 预处理 Markdown 内容,移除不支持的元素
91
+ */
92
+ preprocessMarkdown(content) {
93
+ if (!content || typeof content !== 'string') {
94
+ return content;
95
+ }
96
+
97
+ // 只处理飞书不支持的 Markdown 元素
98
+ let processed = content;
99
+
100
+ // 移除或转换不支持的元素
101
+ processed = processed
102
+ .replace(/```([\s\S]*?)```/g, '[代码块]\n$1\n[代码块结束]') // 代码块转换
103
+ .replace(/`([^`]+)`/g, '【$1】') // 行内代码用特殊括号标记
104
+ .replace(/\*\*(.*?)\*\*/g, '【$1】') // 粗体也用特殊括号标记
105
+ .replace(/\*(.*?)\*/g, '_$1_') // 斜体保持下划线
106
+ .replace(/!\[([^\]]*)\]\([^)]+\)/g, '$1') // 移除图片,保留替代文本
107
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1'); // 链接转为纯文本
108
+
109
+ return processed;
110
+ }
111
+ }
112
+
113
+ module.exports = { CardBuilder };