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,166 @@
#!/usr/bin/env node
/**
* 七牛云存储桶设置更新脚本
*
* 用途:更新存储桶的防覆盖等设置
*
* 用法:
* node update-bucket-setting.js <bucket-name> <setting> <value>
*
* 示例:
* node update-bucket-setting.js mybucket noOverwrite 0 # 允许覆盖
* node update-bucket-setting.js mybucket noOverwrite 1 # 禁止覆盖
*/
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const https = require('https');
const http = require('http');
const DEFAULT_CONFIG_PATH = path.join(process.env.HOME || process.env.USERPROFILE, '.openclaw/credentials/qiniu-config.json');
function loadConfig() {
if (!fs.existsSync(DEFAULT_CONFIG_PATH)) {
throw new Error(`配置文件不存在:${DEFAULT_CONFIG_PATH}`);
}
return JSON.parse(fs.readFileSync(DEFAULT_CONFIG_PATH, 'utf-8'));
}
function hmacSha1(data, secret) {
return crypto.createHmac('sha1', secret).update(data).digest();
}
function urlSafeBase64(data) {
return Buffer.from(data).toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_');
}
function generateAccessToken(accessKey, secretKey, method, path, body = '') {
const host = 'api.qiniu.com';
const contentType = 'application/json';
const signData = `${method} ${path}\nHost: ${host}\nContent-Type: ${contentType}\n\n${body}`;
const signature = hmacSha1(signData, secretKey);
const encodedSign = urlSafeBase64(signature);
return `Qiniu ${accessKey}:${encodedSign}`;
}
function httpRequest(url, options, body = null) {
return new Promise((resolve, reject) => {
const protocol = url.startsWith('https') ? https : http;
const req = protocol.request(url, options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
const json = JSON.parse(data);
resolve({ status: res.statusCode, data: json });
} catch (e) {
resolve({ status: res.statusCode, data: data });
}
});
});
req.on('error', reject);
if (body) {
req.write(body);
}
req.end();
});
}
async function updateBucketSetting(bucketName, setting, value) {
const config = loadConfig();
const bucketConfig = config.buckets['default'];
if (!bucketConfig) {
throw new Error(`默认存储桶配置不存在`);
}
const { accessKey, secretKey, bucket } = bucketConfig;
console.log('🔄 更新存储桶设置...\n');
console.log(`存储桶:${bucket}`);
console.log(`设置项:${setting}`);
console.log(`值:${value}`);
console.log('');
// 七牛云存储桶设置 API
// 文档https://developer.qiniu.com/kodo/api/1313/bucket-settings-update
const updateUrl = `https://api.qiniu.com/buckets/${bucket}/settings`;
const body = JSON.stringify({
[setting]: value === '1' || value === 'true' ? 1 : 0
});
const accessToken = generateAccessToken(accessKey, secretKey, 'PUT', `/buckets/${bucket}/settings`, body);
const options = {
method: 'PUT',
headers: {
'Host': 'api.qiniu.com',
'Content-Type': 'application/json',
'Content-Length': body.length,
'Authorization': accessToken
}
};
console.log('📤 发送更新请求...');
const result = await httpRequest(updateUrl, options, body);
if (result.status !== 200) {
console.error('❌ 更新失败:', result.data);
console.log('\n可能原因:');
console.log('1. AccessKey/SecretKey 权限不足,需要存储桶管理权限');
console.log('2. 存储桶名称不正确');
console.log('3. 设置项不支持通过 API 修改');
return;
}
console.log('✅ 设置已更新成功!\n');
console.log('提示:设置可能需要几分钟生效,请稍后重试上传。');
}
function printUsage() {
console.log(`
用法node update-bucket-setting.js <bucket-name> <setting> <value>
设置项:
noOverwrite - 防覆盖设置 (0=允许覆盖1=禁止覆盖)
示例:
node update-bucket-setting.js mybucket noOverwrite 0 # 允许覆盖
node update-bucket-setting.js mybucket noOverwrite 1 # 禁止覆盖
`);
}
async function main() {
const args = process.argv.slice(2);
if (args.length < 3) {
printUsage();
process.exit(1);
}
const [bucketName, setting, value] = args;
try {
await updateBucketSetting(bucketName, setting, value);
} catch (error) {
console.error('❌ 错误:', error.message);
process.exit(1);
}
}
if (require.main === module) {
main();
}
module.exports = { updateBucketSetting };