Files
openclaw-skill-qiniu/scripts/check-bucket-override.js
daoqi 1aeae9cc51 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
2026-03-07 16:02:18 +08:00

214 lines
7.1 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
/**
* 七牛云存储桶覆盖设置检查脚本
*
* 用途:检查存储桶是否允许覆盖上传
*
* 用法:
* node check-bucket-override.js [bucket-name]
*/
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 = 'kodo.qiniu.com';
const contentType = 'application/json';
// 格式Method Path\nHost: Host\nContent-Type: ContentType\n\nBody
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 checkBucket(bucketName) {
const config = loadConfig();
const bucketConfig = config.buckets[bucketName || 'default'];
if (!bucketConfig) {
throw new Error(`存储桶配置 "${bucketName || 'default'}" 不存在`);
}
const { accessKey, secretKey, bucket, region } = bucketConfig;
console.log('🔍 检查存储桶覆盖设置...\n');
console.log(`存储桶:${bucket}`);
console.log(`区域:${region}`);
console.log(`AccessKey: ${accessKey.substring(0, 4)}...${accessKey.substring(accessKey.length - 4)}`);
console.log('');
// 1. 获取存储桶列表
// 七牛云 API 文档https://developer.qiniu.com/kodo/api/1314/list-buckets
const listBucketsUrl = 'https://kodo.qiniu.com/v2/buckets';
const accessToken = generateAccessToken(accessKey, secretKey, 'GET', '/v2/buckets');
const listOptions = {
method: 'GET',
headers: {
'Host': 'kodo.qiniu.com',
'Authorization': accessToken
}
};
console.log('📋 获取存储桶列表...');
const listResult = await httpRequest(listBucketsUrl, listOptions);
if (listResult.status !== 200) {
console.error('❌ 获取存储桶列表失败:', listResult.data);
return;
}
const buckets = listResult.data;
const targetBucket = buckets.find(b => b.name === bucket);
if (!targetBucket) {
console.error(`❌ 未找到存储桶:${bucket}`);
console.log('\n可用的存储桶:');
buckets.forEach(b => console.log(` - ${b.name}`));
return;
}
console.log('✅ 存储桶存在\n');
// 2. 获取存储桶详细信息
const bucketInfoUrl = `https://kodo.qiniu.com/v2/buckets/${bucket}`;
const bucketInfoToken = generateAccessToken(accessKey, secretKey, 'GET', `/v2/buckets/${bucket}`);
const infoOptions = {
method: 'GET',
headers: {
'Host': 'kodo.qiniu.com',
'Authorization': bucketInfoToken
}
};
console.log('📋 获取存储桶详细信息...');
const infoResult = await httpRequest(bucketInfoUrl, infoOptions);
if (infoResult.status !== 200) {
console.error('❌ 获取存储桶信息失败:', infoResult.data);
console.log('\n⚠ 可能是权限不足,请检查 AccessKey/SecretKey 是否有存储桶管理权限');
return;
}
const bucketInfo = infoResult.data;
console.log('\n📊 存储桶配置信息:');
console.log('─────────────────────────────────────');
console.log(` 名称:${bucketInfo.name || 'N/A'}`);
console.log(` 区域:${bucketInfo.region || bucketInfo.info?.region || 'N/A'}`);
console.log(` 创建时间:${bucketInfo.createdAt || bucketInfo.info?.createdAt || 'N/A'}`);
// 检查覆盖相关设置
const info = bucketInfo.info || bucketInfo;
console.log('\n🔒 安全设置:');
console.log('─────────────────────────────────────');
// 防覆盖设置(关键!)
const noOverwrite = info.noOverwrite !== undefined ? info.noOverwrite : '未设置';
console.log(` 防覆盖:${noOverwrite === true || noOverwrite === 1 ? '❌ 已启用(禁止覆盖)' : '✅ 未启用(允许覆盖)'}`);
// 私有空间设置
const private = info.private !== undefined ? info.private : '未知';
console.log(` 空间类型:${private === true || private === 1 ? '私有空间' : '公共空间'}`);
// 其他设置
if (info.maxSpace !== undefined) {
console.log(` 容量限制:${info.maxSpace} bytes`);
}
console.log('\n💡 解决方案:');
console.log('─────────────────────────────────────');
if (noOverwrite === true || noOverwrite === 1) {
console.log('⚠️ 存储桶已启用"防覆盖"设置,需要关闭才能覆盖上传同名文件。\n');
console.log('关闭方法:');
console.log('1. 登录七牛云控制台https://portal.qiniu.com/');
console.log(`2. 进入"对象存储" → 选择存储桶 "${bucket}"`);
console.log('3. 点击"设置" → "空间设置"');
console.log('4. 找到"防覆盖"选项,关闭它');
console.log('5. 保存设置后重试上传\n');
console.log('或者使用命令行关闭:');
console.log(`node scripts/update-bucket-setting.js ${bucket} noOverwrite 0`);
} else {
console.log('✅ 存储桶允许覆盖上传');
console.log('\n如果仍然无法覆盖可能原因:');
console.log('1. 上传凭证 scope 指定了具体 key但上传时使用了不同的 key');
console.log('2. 上传 API 端点不正确');
console.log('3. 文件正在被其他进程占用');
console.log('\n建议:');
console.log('- 检查上传日志中的实际上传 key 是否一致');
console.log('- 使用相同的完整路径(包括前导 /');
}
}
async function main() {
const bucketName = process.argv[2];
try {
await checkBucket(bucketName);
} catch (error) {
console.error('❌ 错误:', error.message);
process.exit(1);
}
}
if (require.main === module) {
main();
}
module.exports = { checkBucket };