✅ v3 版本 - 支持存储桶和路径选择
功能:
1. 预设路径配置(在 config/qiniu-config.json 中配置 uploadPaths)
2. 文件上传时显示存储桶选择按钮
3. 文件上传时显示路径选择按钮
4. 快速上传按钮(使用默认配置)
5. 取消按钮
配置示例:
{
"buckets": { ... },
"uploadPaths": {
"原文件名": "",
"/config/": "config/",
"/backup/": "backup/",
"/uploads/": "uploads/"
}
}
使用流程:
1. 发送文件
2. 选择存储桶(可选)
3. 选择路径(可选)
4. 点击"使用默认配置上传"
5. 或重新发送文件(使用选择的配置)
This commit is contained in:
186
src/index.js
186
src/index.js
@@ -1,7 +1,8 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* 七牛云上传 - 飞书独立应用 v2
|
||||
* 七牛云上传 - 飞书独立应用 v3
|
||||
* 支持存储桶和路径选择
|
||||
*/
|
||||
|
||||
require('dotenv').config();
|
||||
@@ -23,6 +24,12 @@ function log(...args) {
|
||||
console.log(`[${timestamp}]`, ...args);
|
||||
}
|
||||
|
||||
// 加载完整配置(包括预设路径)
|
||||
function loadFullConfig() {
|
||||
const configPath = path.join(process.cwd(), 'config', 'qiniu-config.json');
|
||||
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
||||
}
|
||||
|
||||
async function handleFeishuEvent(req, res) {
|
||||
const event = req.body;
|
||||
log('📩 收到飞书请求');
|
||||
@@ -39,17 +46,10 @@ async function handleFeishuEvent(req, res) {
|
||||
}
|
||||
}
|
||||
|
||||
// 飞书 v2 schema: event_type 在 header 中
|
||||
const eventType = decryptedEvent.event_type ||
|
||||
decryptedEvent.header?.event_type ||
|
||||
decryptedEvent.type;
|
||||
log('收到飞书事件:', eventType);
|
||||
log('事件结构:', JSON.stringify({
|
||||
has_event_type: !!decryptedEvent.event_type,
|
||||
has_header: !!decryptedEvent.header,
|
||||
header_event_type: decryptedEvent.header?.event_type,
|
||||
has_type: !!decryptedEvent.type
|
||||
}).substring(0, 300));
|
||||
|
||||
if (eventType === 'url_verification') {
|
||||
res.json({ challenge: decryptedEvent.challenge || event.challenge });
|
||||
@@ -97,15 +97,8 @@ async function handleMessage(event) {
|
||||
} else if (text.startsWith('/help') || text.startsWith('/qh')) {
|
||||
await handleHelpCommandV2(chatId, feishu);
|
||||
} else if (messageType === 'file' || messageContent.file_key) {
|
||||
log('🔍 收到文件消息 - 发送存储桶选择卡片');
|
||||
log('🔍 收到文件消息 - 发送配置卡片');
|
||||
await handleFileReceivedWithCard(messageData, feishu, uploader);
|
||||
} else if (text.startsWith('/')) {
|
||||
// 处理路径设置命令
|
||||
log('处理路径设置命令:', text);
|
||||
await feishu.sendMessage(chatId, {
|
||||
msg_type: 'text',
|
||||
content: { text: '💡 请先选择存储桶,然后回复路径\n或使用命令:/upload /路径/文件名 存储桶名' }
|
||||
});
|
||||
} else {
|
||||
await sendWelcomeCard(chatId, feishu);
|
||||
}
|
||||
@@ -131,7 +124,6 @@ async function handleCardInteraction(event) {
|
||||
|
||||
if (!chatId && operator?.open_id) {
|
||||
chatId = operator.open_id;
|
||||
log('使用 operator open_id 作为 chatId:', chatId);
|
||||
}
|
||||
|
||||
log('卡片交互:', action, 'chatId:', chatId);
|
||||
@@ -194,65 +186,28 @@ async function handleCardInteraction(event) {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'select_bucket': {
|
||||
const { file_key, file_name, message_id, chat_id, bucket } = actionData.value;
|
||||
case 'set_bucket': {
|
||||
const { bucket } = actionData.value;
|
||||
log('🪣 选择存储桶:', bucket);
|
||||
|
||||
// 发送确认卡片,带路径输入提示
|
||||
const card = {
|
||||
config: { wide_screen_mode: true },
|
||||
header: {
|
||||
template: 'green',
|
||||
title: { content: '✅ 已选择存储桶', tag: 'plain_text' }
|
||||
},
|
||||
elements: [
|
||||
{
|
||||
tag: 'div',
|
||||
text: {
|
||||
tag: 'lark_md',
|
||||
content: `**存储桶:** ${bucket}\n**文件:** ${file_name}\n\n**请回复消息设置路径:**\n格式:/路径/文件名\n\n例如:/config/test.txt\n\n或直接点击"使用原文件名上传"`
|
||||
}
|
||||
},
|
||||
{
|
||||
tag: 'action',
|
||||
actions: [
|
||||
{
|
||||
tag: 'button',
|
||||
text: { tag: 'plain_text', content: '✅ 使用原文件名上传' },
|
||||
type: 'primary',
|
||||
value: {
|
||||
action: 'confirm_upload',
|
||||
file_key: file_key,
|
||||
file_name: file_name,
|
||||
message_id: message_id,
|
||||
chat_id: chat_id,
|
||||
bucket: bucket
|
||||
}
|
||||
},
|
||||
{
|
||||
tag: 'button',
|
||||
text: { tag: 'plain_text', content: '🔙 重新选择' },
|
||||
type: 'default',
|
||||
value: {
|
||||
action: 'reselect'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
await feishu.sendCard(chatId, card);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'reselect':
|
||||
// 重新发送文件选择卡片(需要用户重新发送文件)
|
||||
await feishu.sendMessage(chatId, {
|
||||
msg_type: 'text',
|
||||
content: { text: '📎 请重新发送文件' }
|
||||
content: { text: `✅ 已选择存储桶:**${bucket}**\n\n请重新发送文件开始上传` }
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'set_path': {
|
||||
const { upload_path, path_label } = actionData.value;
|
||||
const pathDesc = path_label || '原文件名';
|
||||
log('📁 选择路径:', pathDesc);
|
||||
|
||||
await feishu.sendMessage(chatId, {
|
||||
msg_type: 'text',
|
||||
content: { text: `✅ 已选择路径:**${pathDesc}**\n\n请重新发送文件开始上传` }
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case 'cancel_upload':
|
||||
log('取消上传,chatId:', chatId);
|
||||
@@ -298,20 +253,58 @@ async function handleFileReceivedWithCard(messageData, feishu, uploader) {
|
||||
const configData = await uploader2.listConfig();
|
||||
const bucketNames = Object.keys(configData.buckets);
|
||||
|
||||
// 获取预设路径
|
||||
const fullConfig = loadFullConfig();
|
||||
const uploadPaths = fullConfig.uploadPaths || { '原文件名': '' };
|
||||
|
||||
// 构建存储桶选择按钮
|
||||
const bucketButtons = bucketNames.map(name => ({
|
||||
tag: 'button',
|
||||
text: { tag: 'plain_text', content: name },
|
||||
type: 'primary',
|
||||
value: {
|
||||
action: 'set_bucket',
|
||||
bucket: name
|
||||
}
|
||||
}));
|
||||
|
||||
// 构建路径选择按钮
|
||||
const pathButtons = Object.entries(uploadPaths).map(([label, pathValue]) => ({
|
||||
tag: 'button',
|
||||
text: { tag: 'plain_text', content: label },
|
||||
type: 'default',
|
||||
value: {
|
||||
action: 'select_bucket',
|
||||
action: 'set_path',
|
||||
upload_path: pathValue,
|
||||
path_label: label
|
||||
}
|
||||
}));
|
||||
|
||||
// 构建快速上传按钮(使用默认配置)
|
||||
const quickUploadButtons = [
|
||||
{
|
||||
tag: 'button',
|
||||
text: { tag: 'plain_text', content: '✅ 使用默认配置上传' },
|
||||
type: 'primary',
|
||||
value: {
|
||||
action: 'confirm_upload',
|
||||
file_key: fileKey,
|
||||
file_name: fileName,
|
||||
message_id: messageId,
|
||||
chat_id: chatId,
|
||||
bucket: name
|
||||
bucket: 'default',
|
||||
upload_path: ''
|
||||
}
|
||||
}));
|
||||
},
|
||||
{
|
||||
tag: 'button',
|
||||
text: { tag: 'plain_text', content: '❌ 取消' },
|
||||
type: 'default',
|
||||
value: {
|
||||
action: 'cancel_upload'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const card = {
|
||||
config: { wide_screen_mode: true },
|
||||
@@ -324,7 +317,7 @@ async function handleFileReceivedWithCard(messageData, feishu, uploader) {
|
||||
tag: 'div',
|
||||
text: {
|
||||
tag: 'lark_md',
|
||||
content: `**文件名:** ${fileName}\n\n**请选择存储桶:**`
|
||||
content: `**文件名:** ${fileName}\n\n**1️⃣ 选择存储桶:**`
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -338,7 +331,35 @@ async function handleFileReceivedWithCard(messageData, feishu, uploader) {
|
||||
tag: 'div',
|
||||
text: {
|
||||
tag: 'lark_md',
|
||||
content: `💡 **提示:**\n• 选择存储桶后,可以输入路径\n• 使用命令:/upload /路径/文件名 存储桶名`
|
||||
content: `**2️⃣ 选择路径:**`
|
||||
}
|
||||
},
|
||||
{
|
||||
tag: 'action',
|
||||
actions: pathButtons
|
||||
},
|
||||
{
|
||||
tag: 'hr'
|
||||
},
|
||||
{
|
||||
tag: 'div',
|
||||
text: {
|
||||
tag: 'lark_md',
|
||||
content: `**3️⃣ 开始上传:**`
|
||||
}
|
||||
},
|
||||
{
|
||||
tag: 'action',
|
||||
actions: quickUploadButtons
|
||||
},
|
||||
{
|
||||
tag: 'hr'
|
||||
},
|
||||
{
|
||||
tag: 'div',
|
||||
text: {
|
||||
tag: 'lark_md',
|
||||
content: `💡 **提示:**\n• 先选择存储桶和路径\n• 点击"使用默认配置上传"开始\n• 或使用命令:/upload /路径/文件名 存储桶名`
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -386,8 +407,8 @@ async function handleHelpCommandV2(chatId, feishu) {
|
||||
|
||||
📤 上传文件:
|
||||
1. 直接发送文件给我
|
||||
2. 点击确认卡片上的"✅ 确认上传"
|
||||
3. 等待上传完成,获取下载链接
|
||||
2. 选择存储桶和路径
|
||||
3. 点击"使用默认配置上传"
|
||||
|
||||
⚙️ 配置管理:
|
||||
/config list - 查看配置
|
||||
@@ -395,6 +416,7 @@ async function handleHelpCommandV2(chatId, feishu) {
|
||||
|
||||
💡 提示:
|
||||
- 支持多存储桶配置
|
||||
- 支持预设路径
|
||||
- 上传同名文件会自动覆盖
|
||||
` }
|
||||
});
|
||||
@@ -412,18 +434,12 @@ async function sendWelcomeCard(chatId, feishu) {
|
||||
tag: 'div',
|
||||
text: {
|
||||
tag: 'lark_md',
|
||||
content: '你好!我是七牛云上传机器人。\n\n**使用方式:**\n• 直接发送文件给我\n• 点击确认卡片上传\n\n**命令:**\n• /config - 查看配置\n• /help - 查看帮助'
|
||||
content: '你好!我是七牛云上传机器人。\n\n**使用方式:**\n• 直接发送文件给我\n• 选择存储桶和路径\n• 确认上传\n\n**命令:**\n• /config - 查看配置\n• /help - 查看帮助'
|
||||
}
|
||||
},
|
||||
{
|
||||
tag: 'action',
|
||||
actions: [
|
||||
{
|
||||
tag: 'button',
|
||||
text: { tag: 'plain_text', content: '📎 上传文件' },
|
||||
type: 'primary',
|
||||
value: { action: 'upload_file' }
|
||||
},
|
||||
{
|
||||
tag: 'button',
|
||||
text: { tag: 'plain_text', content: '⚙️ 配置' },
|
||||
@@ -487,7 +503,7 @@ setInterval(cleanupTempFiles, 60 * 60 * 1000);
|
||||
log('⏰ 临时文件清理任务已启动(每小时执行一次)');
|
||||
|
||||
app.listen(PORT, () => {
|
||||
log(`🚀 七牛云上传机器人启动 (v2)`);
|
||||
log(`🚀 七牛云上传机器人启动 (v3 - 支持存储桶和路径选择)`);
|
||||
log(`📍 端口:${PORT}`);
|
||||
setTimeout(cleanupTempFiles, 5000);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user