initial: 七牛云上传 OpenClaw Skill

功能特性:
- 支持 /upload, /u 命令上传文件到七牛云
- 支持 /qiniu-config 配置管理
- 支持飞书卡片交互
- 支持指定上传路径和存储桶
- 自动刷新 CDN 缓存
- 支持文件覆盖上传

包含组件:
- OpenClaw 处理器 (openclaw-processor.js)
- 独立监听器 (scripts/feishu-listener.js)
- 核心上传脚本 (scripts/upload-to-qiniu.js)
- 部署脚本 (deploy.sh)
- 完整文档

部署方式:
1. 复制 skill 到 ~/.openclaw/workspace/skills/
2. 配置 ~/.openclaw/credentials/qiniu-config.json
3. 重启 OpenClaw Gateway
This commit is contained in:
daoqi
2026-03-07 16:02:18 +08:00
commit 1aeae9cc51
36 changed files with 6826 additions and 0 deletions

View File

@@ -0,0 +1,410 @@
#!/usr/bin/env node
/**
* 飞书卡片交互服务器
*
* 功能:
* 1. 接收飞书卡片按钮点击回调
* 2. 处理交互逻辑(上传、配置、帮助)
* 3. 回复交互式消息
*
* 使用方式:
* node scripts/feishu-card-server.js [port]
*
* 默认端口3000
*/
const http = require('http');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
// ============ 配置 ============
const PORT = process.argv[2] || 3000;
const CARD_TEMPLATE_PATH = path.join(__dirname, '../cards/upload-card.json');
const QINIU_CONFIG_PATH = path.join(process.env.HOME || process.env.USERPROFILE, '.openclaw/credentials/qiniu-config.json');
// 飞书验证令牌(在飞书开发者后台设置)
const FEISHU_VERIFICATION_TOKEN = process.env.FEISHU_VERIFICATION_TOKEN || 'your_verification_token';
// ============ 工具函数 ============
function loadConfig(configPath = QINIU_CONFIG_PATH) {
if (!fs.existsSync(configPath)) {
throw new Error(`配置文件不存在:${configPath}`);
}
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
}
function loadCardTemplate(templatePath = CARD_TEMPLATE_PATH) {
if (!fs.existsSync(templatePath)) {
throw new Error(`卡片模板不存在:${templatePath}`);
}
return JSON.parse(fs.readFileSync(templatePath, 'utf-8'));
}
function renderCard(template, variables) {
let cardJson = JSON.stringify(template);
for (const [key, value] of Object.entries(variables)) {
cardJson = cardJson.replace(new RegExp(`{{${key}}}`, 'g'), value);
}
return JSON.parse(cardJson);
}
function getRegionName(regionCode) {
const regions = {
'z0': '华东',
'z1': '华北',
'z2': '华南',
'na0': '北美',
'as0': '东南亚'
};
return regions[regionCode] || '未知';
}
// ============ 飞书鉴权 ============
/**
* 验证飞书请求签名
* 文档https://open.feishu.cn/document/ukTMukTMukTM/uYjNwYjL2YDM14SM2ATN
*/
function verifyFeishuSignature(req, body) {
const signature = req.headers['x-feishu-signature'];
if (!signature) return false;
// 简单验证,生产环境需要严格验证
return true;
}
// ============ 卡片交互处理 ============
/**
* 处理卡片按钮点击
*/
async function handleCardInteraction(req, res) {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', async () => {
try {
const data = JSON.parse(body);
// 飞书挑战验证
if (data.type === 'url_verification') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ challenge: data.challenge }));
return;
}
// 处理交互事件
if (data.type === 'interactive_card.action') {
const action = data.action?.value?.action;
const userId = data.user?.user_id;
const openId = data.user?.open_id;
const tenantKey = data.tenant_key;
console.log(`收到卡片交互:${action}, 用户:${userId}`);
let responseCard;
switch (action) {
case 'upload_select':
responseCard = await handleUploadSelect(data);
break;
case 'config_view':
responseCard = await handleConfigView(data);
break;
case 'help':
responseCard = await handleHelp(data);
break;
default:
responseCard = createErrorResponse('未知操作');
}
// 回复卡片
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
type: 'interactive_card.response',
card: responseCard
}));
return;
}
// 未知类型
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'ok' }));
} catch (error) {
console.error('处理交互失败:', error);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: error.message }));
}
});
}
/**
* 处理"选择文件上传"按钮
*/
async function handleUploadSelect(data) {
const config = loadConfig();
const bucketName = data.action?.value?.bucket || 'default';
const bucketConfig = config.buckets[bucketName];
if (!bucketConfig) {
return createErrorResponse(`存储桶 "${bucketName}" 不存在`);
}
// 回复引导用户上传文件
return {
config: {
wide_screen_mode: true
},
header: {
template: "green",
title: {
content: "📎 选择文件",
tag: "plain_text"
}
},
elements: [
{
tag: "div",
text: {
content: `请点击下方按钮选择要上传的文件,文件将上传到 **${bucketName}** 存储桶。`,
tag: "lark_md"
}
},
{
tag: "action",
actions: [
{
tag: "button",
text: {
content: "📁 选择文件",
tag: "plain_text"
},
type: "primary",
url: "feishu://attachment/select" // 飞书内部协议,触发文件选择
}
]
}
]
};
}
/**
* 处理"查看配置"按钮
*/
async function handleConfigView(data) {
const config = loadConfig();
let bucketList = '';
for (const [name, bucket] of Object.entries(config.buckets)) {
bucketList += `**${name}**: ${bucket.bucket} (${bucket.region})\n`;
}
return {
config: {
wide_screen_mode: true
},
header: {
template: "blue",
title: {
content: "📋 当前配置",
tag: "plain_text"
}
},
elements: [
{
tag: "div",
text: {
content: bucketList || '暂无配置',
tag: "lark_md"
}
},
{
tag: "hr"
},
{
tag: "note",
elements: [
{
tag: "plain_text",
content: `配置文件:${QINIU_CONFIG_PATH}`
}
]
}
]
};
}
/**
* 处理"帮助"按钮
*/
async function handleHelp(data) {
return {
config: {
wide_screen_mode: true
},
header: {
template: "grey",
title: {
content: "❓ 帮助",
tag: "plain_text"
}
},
elements: [
{
tag: "div",
text: {
content: `**七牛云上传帮助**
📤 **上传文件**
- 点击"选择文件上传"按钮
- 选择要上传的文件
- 自动上传到七牛云
⚙️ **快捷命令**
- \`/u\` - 快速上传
- \`/qc\` - 查看配置
- \`/qh\` - 显示帮助
📦 **存储桶**
- 支持多存储桶配置
- 上传时可指定目标桶`,
tag: "lark_md"
}
}
]
};
}
/**
* 创建错误响应
*/
function createErrorResponse(message) {
return {
config: {
wide_screen_mode: true
},
header: {
template: "red",
title: {
content: "❌ 错误",
tag: "plain_text"
}
},
elements: [
{
tag: "div",
text: {
content: message,
tag: "lark_md"
}
}
]
};
}
// ============ 主页面(测试用) ============
function serveHomePage(res) {
const html = `
<!DOCTYPE html>
<html>
<head>
<title>七牛云上传 - 飞书卡片服务器</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
h1 { color: #333; }
.status { padding: 10px; background: #e8f5e9; border-radius: 4px; margin: 20px 0; }
.config { background: #f5f5f5; padding: 15px; border-radius: 4px; }
code { background: #eee; padding: 2px 6px; border-radius: 3px; }
</style>
</head>
<body>
<h1>🍙 七牛云上传 - 飞书卡片服务器</h1>
<div class="status">
✅ 服务器运行中
<br>端口:<code>${PORT}</code>
</div>
<div class="config">
<h3>配置信息</h3>
<p>卡片模板:<code>${CARD_TEMPLATE_PATH}</code></p>
<p>七牛配置:<code>${QINIU_CONFIG_PATH}</code></p>
</div>
<h3>飞书开发者后台配置</h3>
<ol>
<li>请求网址:<code>http://你的服务器IP:${PORT}/feishu/card</code></li>
<li>数据加密方式:选择"不加密"</li>
<li>验证令牌:在环境变量中设置 <code>FEISHU_VERIFICATION_TOKEN</code></li>
</ol>
<h3>测试</h3>
<p>使用 curl 测试:</p>
<pre><code>curl -X POST http://localhost:${PORT}/feishu/card \\
-H "Content-Type: application/json" \\
-d '{"type":"url_verification","challenge":"test123"}'</code></pre>
</body>
</html>
`;
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(html);
}
// ============ HTTP 服务器 ============
const server = http.createServer((req, res) => {
console.log(`${new Date().toISOString()} ${req.method} ${req.url}`);
// CORS 头(飞书回调需要)
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Feishu-Signature');
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
// 主页
if (req.url === '/' || req.url === '/health') {
serveHomePage(res);
return;
}
// 卡片交互回调
if (req.url === '/feishu/card' && req.method === 'POST') {
handleCardInteraction(req, res);
return;
}
// 404
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
});
// ============ 启动服务器 ============
server.listen(PORT, () => {
console.log(`🍙 七牛云卡片服务器已启动`);
console.log(`端口:${PORT}`);
console.log(`主页http://localhost:${PORT}/`);
console.log(`回调地址http://localhost:${PORT}/feishu/card`);
console.log(`\n在飞书开发者后台配置请求网址为http://你的服务器IP:${PORT}/feishu/card`);
});
// 优雅退出
process.on('SIGINT', () => {
console.log('\n正在关闭服务器...');
server.close(() => {
console.log('服务器已关闭');
process.exit(0);
});
});