Files
qiniu-feishu-bot/src/index.js
饭团 a18ddc028c 清理调试代码和废弃文件
清理内容:
- 删除废弃文档:DEPLOY.md, FEISHU_PERMISSIONS.md, NGINX.md, WEBSOCKET.md, WINDOWS.md
- 删除废弃服务文件:qiniu-bot.service, start.bat, start.sh
- 删除废弃代码目录:src/cards/
- 优化日志:仅非生产环境输出调试日志
- 保留核心文件:Dockerfile, .env*, README.md, pm2.config.cjs

生产环境设置:
- NODE_ENV=production 时不输出调试日志
- NODE_ENV=development 时输出完整日志
2026-03-06 12:22:31 +08:00

942 lines
28 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* 七牛云上传 - 飞书独立应用 v5
* 简化流程:上传配置 + 一键上传
*/
require('dotenv').config();
const express = require('express');
const path = require('path');
const fs = require('fs');
const { FeishuAPI } = require('./feishu-api');
const { QiniuUploader } = require('./qiniu-uploader');
const app = express();
const PORT = process.env.PORT || 3030;
app.use(express.json());
// 生产环境日志(仅关键信息)
function log(...args) {
if (process.env.NODE_ENV !== 'production') {
const timestamp = new Date().toISOString();
console.log(`[${timestamp}]`, ...args);
}
}
// 加载完整配置
function loadFullConfig() {
const configPath = path.join(process.cwd(), 'config', 'qiniu-config.json');
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
}
// 用户状态存储
const userStates = {};
function getUserState(chatId) {
return userStates[chatId] || {};
}
function setUserState(chatId, state) {
userStates[chatId] = { ...userStates[chatId], ...state };
setTimeout(() => { delete userStates[chatId]; }, 5 * 60 * 1000);
}
function clearUserState(chatId) {
delete userStates[chatId];
}
async function handleFeishuEvent(req, res) {
const event = req.body;
log('📩 收到飞书请求');
let decryptedEvent = event;
if (event.encrypt) {
try {
const { decrypt } = require('@larksuiteoapi/node-sdk');
decryptedEvent = decrypt(event.encrypt, process.env.FEISHU_ENCRYPT_KEY);
} catch (e) {
res.status(500).send('Decrypt error');
return;
}
}
const eventType = decryptedEvent.event_type ||
decryptedEvent.header?.event_type ||
decryptedEvent.type;
if (eventType === 'url_verification') {
res.json({ challenge: decryptedEvent.challenge || event.challenge });
return;
}
if (eventType === 'im.message.receive_v1') {
await handleMessage(decryptedEvent);
}
if (eventType === 'card.action.trigger') {
await handleCardInteraction(decryptedEvent);
res.status(200).json({});
return;
}
res.status(200).send('OK');
}
async function handleMessage(event) {
const messageData = event.event?.message || event.message;
if (!messageData) return;
const messageContent = JSON.parse(messageData.content);
const text = messageContent.text || '';
const chatId = messageData.chat_id;
const messageType = messageData.message_type || 'text';
const feishu = new FeishuAPI();
const uploader = new QiniuUploader();
if (text.startsWith('/upload') || text.startsWith('/u ')) {
// 显示上传配置卡片
await showProfileCard(chatId, feishu, uploader);
} else if (text.startsWith('/config') || text.startsWith('/qc ')) {
await handleConfigCommandV2(messageData, messageContent, feishu, uploader);
} else if (text.startsWith('/path')) {
await handlePathCommandV2(messageData, messageContent, feishu);
} else if (text.startsWith('/profile')) {
await handleProfileCommandV2(messageData, messageContent, feishu);
} else if (text.startsWith('/help') || text.startsWith('/qh')) {
await handleHelpCommandV2(chatId, feishu);
} else if (text.startsWith('/skill')) {
await handleSkillCommandV2(chatId, feishu, text);
} else if (messageType === 'file' || messageContent.file_key) {
// 收到文件,显示配置选择
await handleFileReceived(messageData, feishu, uploader);
} else {
await sendWelcomeCard(chatId, feishu);
}
}
async function handleCardInteraction(event) {
const eventData = event.event;
const actionData = eventData?.action;
const message = eventData?.message;
if (!actionData) return;
const action = actionData.value?.action;
let chatId = message?.chat_id || actionData.value?.chat_id;
if (!chatId && eventData?.operator?.open_id) {
chatId = eventData.operator.open_id;
}
log('卡片交互:', action, 'chatId:', chatId);
const feishu = new FeishuAPI();
const uploader = new QiniuUploader();
switch (action) {
case 'start_upload':
await showProfileCard(chatId, feishu, uploader);
break;
case 'select_profile': {
const { profile_name, bucket, path_key } = actionData.value;
log('📋 选择上传配置:', profile_name);
// path_key 是预设路径的名称,需要从配置中获取实际路径
setUserState(chatId, {
profile_name,
bucket,
path_key
});
await feishu.sendMessage(chatId, {
msg_type: 'text',
content: { text: `✅ 已选择配置:**${profile_name}**\n\n📤 请发送文件` }
});
break;
}
case 'upload_with_profile': {
// 从配置卡片直接上传(需要先发送文件)
const { profile_name, bucket, path_key } = actionData.value;
const state = getUserState(chatId);
if (!state.file_key) {
await feishu.sendMessage(chatId, {
msg_type: 'text',
content: { text: '📎 请先发送文件' }
});
return;
}
// 从预设路径配置中获取实际路径
const fullConfig = loadFullConfig();
const uploadPaths = fullConfig.uploadPaths || {};
const upload_path = uploadPaths[path_key] || '';
await doUpload(chatId, feishu, uploader, {
file_key: state.file_key,
file_name: state.file_name,
message_id: state.message_id,
bucket,
upload_path,
path_label: profile_name,
path_key
});
clearUserState(chatId);
break;
}
case 'confirm_upload': {
const { file_key, file_name, message_id, bucket, path_key, path_label } = actionData.value;
// 从预设路径配置中获取实际路径
const fullConfig = loadFullConfig();
const uploadPaths = fullConfig.uploadPaths || {};
const upload_path = uploadPaths[path_key] || '';
await doUpload(chatId, feishu, uploader, {
file_key, file_name, message_id, bucket, upload_path, path_label, path_key
});
clearUserState(chatId);
break;
}
case 'cancel':
clearUserState(chatId);
await feishu.sendMessage(chatId, {
msg_type: 'text',
content: { text: '❌ 已取消' }
});
break;
case 'config': {
const configData = await uploader.listConfig();
await feishu.sendCard(chatId, createConfigCard(configData));
break;
}
case 'help':
await handleHelpCommandV2(chatId, feishu);
break;
}
}
// 显示上传配置卡片
async function showProfileCard(chatId, feishu, uploader) {
const fullConfig = loadFullConfig();
const profiles = fullConfig.uploadProfiles || {};
const profileButtons = Object.entries(profiles).map(([name, config]) => {
// config.path 是预设路径的名称(键)
const pathKey = config.path || '';
const uploadPaths = fullConfig.uploadPaths || {};
const pathValue = uploadPaths[pathKey] || '';
const pathDisplay = pathValue || '(原文件名)';
return {
tag: 'button',
text: { tag: 'plain_text', content: `${name}` },
type: 'primary',
value: {
action: 'select_profile',
profile_name: name,
bucket: config.bucket,
path_key: pathKey
}
};
});
const card = {
config: { wide_screen_mode: true },
header: {
template: 'blue',
title: { content: '📤 选择上传配置', tag: 'plain_text' }
},
elements: [
{
tag: 'div',
text: {
tag: 'lark_md',
content: '**选择一个上传配置,然后发送文件:**'
}
},
{
tag: 'action',
actions: profileButtons
},
{
tag: 'hr'
},
{
tag: 'div',
text: {
tag: 'lark_md',
content: '💡 **提示:**\n• 选择配置后发送文件\n• 或直接发送文件后选择配置'
}
},
{
tag: 'action',
actions: [
{
tag: 'button',
text: { tag: 'plain_text', content: '⚙️ 配置' },
type: 'default',
value: { action: 'config' }
},
{
tag: 'button',
text: { tag: 'plain_text', content: '❓ 帮助' },
type: 'default',
value: { action: 'help' }
}
]
}
]
};
await feishu.sendCard(chatId, card);
}
// 处理文件接收
async function handleFileReceived(messageData, feishu, uploader) {
const chatId = messageData.chat_id;
const messageId = messageData.message_id;
const messageContent = JSON.parse(messageData.content);
const fileKey = messageContent.file_key;
const fileName = messageContent.file_name;
if (!fileKey) return;
// 保存文件信息到状态
setUserState(chatId, {
file_key: fileKey,
file_name: fileName,
message_id: messageId
});
const state = getUserState(chatId);
// 如果已选择配置,显示确认卡片
if (state.bucket && state.upload_path !== undefined) {
await showConfirmCard(chatId, feishu, {
file_key: fileKey,
file_name: fileName,
message_id: messageId,
bucket: state.bucket,
upload_path: state.upload_path,
path_label: state.profile_name || '自定义'
});
} else {
// 未选择配置,显示配置选择卡片
const fullConfig = loadFullConfig();
const profiles = fullConfig.uploadProfiles || {};
const uploadPaths = fullConfig.uploadPaths || {};
const profileButtons = Object.entries(profiles).map(([name, config]) => {
// config.path 是预设路径的名称(键)
const pathKey = config.path || '';
return {
tag: 'button',
text: { tag: 'plain_text', content: `${name}` },
type: 'primary',
value: {
action: 'confirm_upload',
file_key: fileKey,
file_name: fileName,
message_id: messageId,
chat_id: chatId,
bucket: config.bucket,
path_key: pathKey,
path_label: name
}
};
});
const card = {
config: { wide_screen_mode: true },
header: {
template: 'blue',
title: { content: '📎 文件已收到', tag: 'plain_text' }
},
elements: [
{
tag: 'div',
text: {
tag: 'lark_md',
content: `**文件:** ${fileName}\n\n**选择配置后确认上传:**`
}
},
{
tag: 'action',
actions: profileButtons
},
{
tag: 'hr'
},
{
tag: 'div',
text: {
tag: 'lark_md',
content: '💡 点击配置按钮直接上传'
}
},
{
tag: 'action',
actions: [
{
tag: 'button',
text: { tag: 'plain_text', content: '❌ 取消' },
type: 'default',
value: { action: 'cancel' }
}
]
}
]
};
await feishu.sendCard(chatId, card);
}
}
// 显示确认卡片
async function showConfirmCard(chatId, feishu, info) {
const { file_name, bucket, path_key, path_label } = info;
// 从预设路径配置中获取实际路径
const fullConfig = loadFullConfig();
const uploadPaths = fullConfig.uploadPaths || {};
const upload_path = uploadPaths[path_key] || '';
let targetKey = upload_path || file_name;
if (targetKey.startsWith('/')) targetKey = targetKey.substring(1);
const card = {
config: { wide_screen_mode: true },
header: {
template: 'green',
title: { content: '✅ 确认上传', tag: 'plain_text' }
},
elements: [
{
tag: 'div',
text: {
tag: 'lark_md',
content: `**文件:** ${file_name}\n**配置:** ${path_label}\n**存储桶:** ${bucket}\n**路径:** ${targetKey || '(原文件名)'}\n\n点击"确认上传"开始上传`
}
},
{
tag: 'action',
actions: [
{
tag: 'button',
text: { tag: 'plain_text', content: '✅ 确认上传' },
type: 'primary',
value: {
action: 'confirm_upload',
file_key: info.file_key,
file_name: info.file_name,
message_id: info.message_id,
chat_id: chatId,
bucket,
path_key,
path_label
}
},
{
tag: 'button',
text: { tag: 'plain_text', content: '❌ 取消' },
type: 'default',
value: { action: 'cancel' }
}
]
}
]
};
await feishu.sendCard(chatId, card);
}
// 执行上传
async function doUpload(chatId, feishu, uploader, info) {
const { file_key, file_name, message_id, bucket, upload_path, path_label } = info;
let targetKey = upload_path || file_name;
if (targetKey.startsWith('/')) targetKey = targetKey.substring(1);
log('📤 开始上传:', file_name, '→', bucket, '/', targetKey);
try {
await feishu.sendMessage(chatId, {
msg_type: 'text',
content: { text: `📥 正在下载:${file_name}` }
});
const tempFile = await feishu.downloadFile(file_key, message_id, chatId);
log('✅ 文件下载完成:', tempFile);
await feishu.sendMessage(chatId, {
msg_type: 'text',
content: { text: `📤 上传中:${targetKey}${bucket}` }
});
const result = await uploader.upload(tempFile, targetKey, bucket);
await uploader.refreshCDN(bucket, targetKey);
await feishu.sendMessage(chatId, {
msg_type: 'text',
content: {
text: `✅ 上传成功!\n\n` +
`📦 文件:${targetKey}\n` +
`🔗 链接:${result.url}\n` +
`💾 原文件:${file_name}\n` +
`🪣 存储桶:${bucket}\n` +
`📁 配置:${path_label}`
}
});
fs.unlinkSync(tempFile);
log('🗑️ 临时文件已清理');
} catch (error) {
log('❌ 上传失败:', error.message);
await feishu.sendMessage(chatId, {
msg_type: 'text',
content: { text: `❌ 上传失败:${error.message}` }
});
}
}
async function handleConfigCommandV2(message, content, feishu, uploader) {
const chatId = message.chat_id;
const text = content.text || '';
const args = text.replace(/^\/(config|qc)\s*/i, '').trim().split(/\s+/);
const subCommand = args[0];
try {
if (subCommand === 'list' || !subCommand) {
const configData = await uploader.listConfig();
await feishu.sendCard(chatId, createBucketsListCard(configData));
} else if (subCommand === 'set') {
const [keyPath, value] = args.slice(1);
await uploader.setConfigValue(keyPath, value);
await feishu.sendMessage(chatId, {
msg_type: 'text',
content: { text: `✅ 已设置 ${keyPath} = ${value}` }
});
}
} catch (error) {
await feishu.sendMessage(chatId, {
msg_type: 'text',
content: { text: `❌ 配置失败:${error.message}` }
});
}
}
async function handlePathCommandV2(message, content, feishu) {
const chatId = message.chat_id;
const text = content.text || '';
const args = text.replace(/^\/path\s*/i, '').trim().split(/\s+/);
const subCommand = args[0];
const configPath = path.join(process.cwd(), 'config', 'qiniu-config.json');
try {
const fullConfig = loadFullConfig();
if (subCommand === 'list') {
const paths = fullConfig.uploadPaths || {};
await feishu.sendCard(chatId, createPathsListCard(paths));
} else if (subCommand === 'add') {
const name = args[1];
const pathValue = args[2];
fullConfig.uploadPaths[name] = pathValue;
fs.writeFileSync(configPath, JSON.stringify(fullConfig, null, 2));
await feishu.sendMessage(chatId, {
msg_type: 'text',
content: { text: `✅ 已添加预设路径:**${name}** → ${pathValue}` }
});
}
} catch (error) {
await feishu.sendMessage(chatId, {
msg_type: 'text',
content: { text: `❌ 路径管理失败:${error.message}` }
});
}
}
async function handleProfileCommandV2(message, content, feishu) {
const chatId = message.chat_id;
const text = content.text || '';
const args = text.replace(/^\/profile\s*/i, '').trim().split(/\s+/);
const subCommand = args[0];
const configPath = path.join(process.cwd(), 'config', 'qiniu-config.json');
try {
const fullConfig = loadFullConfig();
if (subCommand === 'list' || !subCommand) {
const profiles = fullConfig.uploadProfiles || {};
const uploadPaths = fullConfig.uploadPaths || {};
await feishu.sendCard(chatId, createProfilesListCard(profiles, uploadPaths));
} else if (subCommand === 'add') {
// /profile add <名称> <存储桶> [路径键名]
if (args.length < 3) {
throw new Error('用法:/profile add <名称> <存储桶> [路径键名]\n示例/profile add IPA 上传 default ipa');
}
const name = args[1];
const bucket = args[2];
const pathKey = args[3] || '';
// 验证存储桶是否存在
if (!fullConfig.buckets[bucket]) {
throw new Error(`存储桶 "${bucket}" 不存在,可用:${Object.keys(fullConfig.buckets).join(', ')}`);
}
// 验证路径键名是否存在(如果有提供)
if (pathKey && (!fullConfig.uploadPaths || !fullConfig.uploadPaths[pathKey])) {
const availablePaths = Object.keys(fullConfig.uploadPaths || {}).join(', ');
throw new Error(`路径 "${pathKey}" 不存在,可用:${availablePaths || '无'}`);
}
fullConfig.uploadProfiles = fullConfig.uploadProfiles || {};
fullConfig.uploadProfiles[name] = {
bucket: bucket,
path: pathKey // 存储路径键名,不是路径值
};
fs.writeFileSync(configPath, JSON.stringify(fullConfig, null, 2));
const pathDisplay = pathKey ? `${pathKey} (${fullConfig.uploadPaths[pathKey]})` : '(原文件名)';
await feishu.sendMessage(chatId, {
msg_type: 'text',
content: { text: `✅ 已添加上传配置:**${name}**\n存储桶:${bucket}\n路径:${pathDisplay}` }
});
} else if (subCommand === 'remove' || subCommand === 'del') {
if (args.length < 2) {
throw new Error('用法:/profile remove <名称>');
}
const name = args[1];
if (!fullConfig.uploadProfiles || !fullConfig.uploadProfiles[name]) {
throw new Error(`上传配置 "${name}" 不存在`);
}
delete fullConfig.uploadProfiles[name];
fs.writeFileSync(configPath, JSON.stringify(fullConfig, null, 2));
await feishu.sendMessage(chatId, {
msg_type: 'text',
content: { text: `✅ 已删除上传配置:**${name}**` }
});
} else {
throw new Error(`未知命令:${subCommand}\n可用命令list, add, remove`);
}
} catch (error) {
await feishu.sendMessage(chatId, {
msg_type: 'text',
content: { text: `❌ 配置管理失败:${error.message}` }
});
}
}
async function handleHelpCommandV2(chatId, feishu) {
const helpCard = {
config: { wide_screen_mode: true },
header: {
template: 'green',
title: { content: '❓ 使用帮助', tag: 'plain_text' }
},
elements: [
{
tag: 'div',
text: {
tag: 'lark_md',
content: '**🍙 七牛云上传机器人**\n快速上传文件到七牛云存储'
}
},
{
tag: 'hr'
},
{
tag: 'div',
text: {
tag: 'lark_md',
content: '**📤 上传方式**\n\n**方式 1选择配置 → 发送文件**\n1⃣ 发送 /upload\n2⃣ 选择上传配置\n3⃣ 发送文件\n4⃣ 确认上传\n\n**方式 2发送文件 → 选择配置**\n1⃣ 直接发送文件\n2⃣ 选择上传配置\n3⃣ 确认上传'
}
},
{
tag: 'hr'
},
{
tag: 'div',
text: {
tag: 'lark_md',
content: '**⚙️ 配置命令**\n\n• /config list - 查看存储桶\n• /profile list - 查看上传配置\n• /profile add <名称> <桶> [路径] - 添加配置\n• /profile remove <名称> - 删除配置\n\n**📁 路径命令**\n\n• /path list - 查看预设路径\n• /path add <名称> <路径> - 添加路径'
}
},
{
tag: 'hr'
},
{
tag: 'div',
text: {
tag: 'lark_md',
content: '**💡 示例**\n\n`/profile add IPA 上传 default ipa`\n`/path add backup /backup/`\n\n**提示:** 上传同名文件会自动覆盖'
}
},
{
tag: 'action',
actions: [
{
tag: 'button',
text: { tag: 'plain_text', content: '📤 上传文件' },
type: 'primary',
value: { action: 'start_upload' }
},
{
tag: 'button',
text: { tag: 'plain_text', content: '⚙️ 配置' },
type: 'default',
value: { action: 'config' }
}
]
}
]
};
await feishu.sendCard(chatId, helpCard);
}
async function handleSkillCommandV2(chatId, feishu, text) {
const args = text.replace(/^\/skill\s*/i, '').trim().split(/\s+/);
const subCommand = args[0];
if (subCommand === 'list' || !subCommand) {
const skillCard = {
config: { wide_screen_mode: true },
header: {
template: 'blue',
title: { content: '🔧 可用技能列表', tag: 'plain_text' }
},
elements: [
{
tag: 'div',
text: {
tag: 'lark_md',
content: '**📦 七牛云上传** `qiniu-uploader`\n\n七牛云文件上传和管理。支持命令触发和飞书卡片交互。\n\n**命令:**\n• `/upload` - 上传文件\n• `/qiniu-config` - 管理配置\n• `/qiniu-help` - 查看帮助'
}
},
{ tag: 'hr' },
{
tag: 'div',
text: {
tag: 'lark_md',
content: '**🔍 搜索工具** `searxng`\n\n隐私保护的元搜索引擎。使用本地 SearXNG 实例搜索。\n\n**命令:**\n• `/search` - 搜索内容\n• `/image` - 搜索图片'
}
},
{ tag: 'hr' },
{
tag: 'div',
text: {
tag: 'lark_md',
content: '💡 **提示:** 使用 `/skill <名称>` 查看技能详情'
}
}
]
};
await feishu.sendCard(chatId, skillCard);
}
}
async function sendWelcomeCard(chatId, feishu) {
const card = {
config: { wide_screen_mode: true },
header: {
template: 'blue',
title: { content: '🍙 七牛云上传机器人', tag: 'plain_text' }
},
elements: [
{
tag: 'div',
text: {
tag: 'lark_md',
content: '你好!我是七牛云上传机器人,帮你快速上传文件到七牛云。\n\n**📤 上传方式:**\n• 发送 /upload 选择配置\n• 直接发送文件\n\n**⚙️ 常用命令:**\n• /config - 查看存储桶配置\n• /path - 管理预设路径\n• /profile - 管理上传配置'
}
},
{
tag: 'action',
actions: [
{
tag: 'button',
text: { tag: 'plain_text', content: '📤 上传文件' },
type: 'primary',
value: { action: 'start_upload' }
},
{
tag: 'button',
text: { tag: 'plain_text', content: '❓ 帮助' },
type: 'default',
value: { action: 'help' }
}
]
},
{
tag: 'hr'
},
{
tag: 'div',
text: {
tag: 'lark_md',
content: '💡 **提示:** 点击"帮助"查看详细使用指南'
}
}
]
};
await feishu.sendCard(chatId, card);
}
// 存储桶列表卡片(表格形式)
function createBucketsListCard(configData) {
const buckets = configData.buckets || {};
const entries = Object.entries(buckets);
if (entries.length === 0) {
return {
config: { wide_screen_mode: true },
header: {
template: 'grey',
title: { content: '⚙️ 七牛云存储桶配置', tag: 'plain_text' }
},
elements: [{ tag: 'div', text: { tag: 'lark_md', content: '暂无配置' } }]
};
}
// 构建表格内容
let tableContent = '| 名称 | 存储桶 | 区域 | CDN 域名 |\n|------|--------|------|----------|\n';
for (const [name, bucket] of entries) {
const domain = bucket.domain.length > 30 ? bucket.domain.substring(0, 27) + '...' : bucket.domain;
tableContent += `| ${name} | ${bucket.bucket} | ${bucket.region} | ${domain} |\n`;
}
return {
config: { wide_screen_mode: true },
header: {
template: 'green',
title: { content: '⚙️ 七牛云存储桶配置', tag: 'plain_text' }
},
elements: [
{
tag: 'div',
text: {
tag: 'lark_md',
content: tableContent
}
}
]
};
}
// 预设路径列表卡片(表格形式)
function createPathsListCard(paths) {
const entries = Object.entries(paths);
if (entries.length === 0) {
return {
config: { wide_screen_mode: true },
header: {
template: 'grey',
title: { content: '📁 预设路径列表', tag: 'plain_text' }
},
elements: [{ tag: 'div', text: { tag: 'lark_md', content: '暂无预设路径' } }]
};
}
// 构建表格内容
let tableContent = '| 名称 | 路径 |\n|------|------|\n';
for (const [name, pathValue] of entries) {
const pathDisplay = pathValue || '(原文件名)';
tableContent += `| **${name}** | ${pathDisplay} |\n`;
}
return {
config: { wide_screen_mode: true },
header: {
template: 'blue',
title: { content: '📁 预设路径列表', tag: 'plain_text' }
},
elements: [
{
tag: 'div',
text: {
tag: 'lark_md',
content: tableContent
}
}
]
};
}
// 上传配置模板列表卡片(表格形式)
function createProfilesListCard(profiles, uploadPaths) {
const entries = Object.entries(profiles);
if (entries.length === 0) {
return {
config: { wide_screen_mode: true },
header: {
template: 'grey',
title: { content: '📤 上传配置模板', tag: 'plain_text' }
},
elements: [{ tag: 'div', text: { tag: 'lark_md', content: '暂无上传配置模板' } }]
};
}
// 构建表格内容
let tableContent = '| 名称 | 存储桶 | 路径 |\n|------|--------|------|\n';
for (const [name, config] of entries) {
const pathKey = config.path || '';
const pathValue = uploadPaths && uploadPaths[pathKey] ? uploadPaths[pathKey] : '(原文件名)';
const pathDisplay = pathKey ? `${pathKey}${pathValue}` : '(原文件名)';
tableContent += `| **${name}** | ${config.bucket} | ${pathDisplay} |\n`;
}
return {
config: { wide_screen_mode: true },
header: {
template: 'blue',
title: { content: '📤 上传配置模板', tag: 'plain_text' }
},
elements: [
{
tag: 'div',
text: {
tag: 'lark_md',
content: tableContent
}
}
]
};
}
// 旧的配置卡片(保留兼容)
function createConfigCard(configData) {
return createBucketsListCard(configData);
}
app.post('/feishu/event', handleFeishuEvent);
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString(), port: PORT });
});
app.listen(PORT, () => {
log(`🚀 七牛云上传机器人启动 (v5 - 简化流程)`);
log(`📍 端口:${PORT}`);
});