commit 0140cf9ae75c510dee47d1b4a6cc3a7c578a02f9 Author: OpenClaw Skill Sync Bot Date: Fri Apr 10 19:54:19 2026 +0800 auto-sync: tencent-cos-upload 2026-04-10_19:54 diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..2594591 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,94 @@ +--- +name: tencent-cos-upload +description: 上传文件到腾讯云COS并生成可直接访问的URL。当需要将本地文件(图片、视频、音频、文档等)上传到腾讯云对象存储,并获取公开可访问链接时使用。触发场景:上传文件到COS、生成文件外链、存储媒体资源、备份文件到云端。 +--- + +# 腾讯云COS文件上传 + +将本地文件上传到腾讯云COS,返回可直接访问的URL。 + +## 前置依赖 + +```bash +pip3 install cos-python-sdk-v5 --break-system-packages +``` + +## 使用方式 + +### 方式一:调用脚本(推荐) + +```bash +python3 scripts/cos_upload.py [--content-type ] +``` + +- `local_file`:本地文件路径 +- `cos_key`:COS上的存储路径(如 `vala_llm/data/image.png`) +- `--content-type`:可选,MIME类型(如 `image/png`),不指定则自动检测 + +输出一行URL:`https:///` + +示例: +```bash +python3 scripts/cos_upload.py /tmp/photo.png vala_llm/user_feedback/image/2026-04-10/abc123.png +# 输出: https://static.valavala.com/vala_llm/user_feedback/image/2026-04-10/abc123.png +``` + +### 方式二:Python代码引用 + +```python +import sys +sys.path.insert(0, '/root/.openclaw/skills/tencent-cos-upload/scripts') +from cos_upload import CosUploader + +uploader = CosUploader() +url = uploader.upload('/tmp/photo.png', 'vala_llm/images/photo.png') +print(url) # https://static.valavala.com/vala_llm/images/photo.png +``` + +批量上传: +```python +results = uploader.upload_batch([ + ('/tmp/a.png', 'path/a.png'), + ('/tmp/b.mp4', 'path/b.mp4'), +]) +# results = [('path/a.png', 'https://...'), ('path/b.png', 'https://...')] +``` + +## 配置 + +凭证从 `secrets.md` 的腾讯云COS部分读取,脚本中通过硬编码加载(非环境变量)。 + +如需修改配置(换桶、换域名),编辑 `scripts/cos_upload.py` 顶部的常量。 + +## COS路径规范 + +建议按 `{业务}/{类型}/{日期}/` 组织: +``` +vala_llm/ +├── user_feedback/ +│ ├── image/2026-04-10/ +│ ├── video/2026-04-10/ +│ ├── audio/2026-04-10/ +│ └── file/2026-04-10/ +└── asr_audio_backup/ + ├── online/20260410/ + └── test/20260410/ +``` + +## 文件名规范 + +- 避免中文,使用纯ASCII字符(字母、数字、短横线、下划线) +- 推荐格式:`{唯一ID}{扩展名}`,如 `abc123def456.png` + +## 常见MIME类型 + +| 扩展名 | Content-Type | +|--------|-------------| +| .png | image/png | +| .jpg/.jpeg | image/jpeg | +| .mp4 | video/mp4 | +| .mov | video/quicktime | +| .mp3 | audio/mpeg | +| .ogg | audio/ogg | +| .wav | audio/wav | +| .pdf | application/pdf | diff --git a/scripts/cos_upload.py b/scripts/cos_upload.py new file mode 100644 index 0000000..c02bc50 --- /dev/null +++ b/scripts/cos_upload.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +""" +腾讯云COS文件上传工具 +用法: + python3 cos_upload.py [--content-type ] +""" +import os +import sys +import mimetypes +import argparse +from qcloud_cos import CosConfig, CosS3Client + +# ============ 配置(修改这里切换桶/域名)============ +COS_SECRET_ID = "AKIDpbWMzOYaP2UltHWccS4TJJDxX1Pfkvzt" +COS_SECRET_KEY = "ythOwofOxvtklL3XpxsaChEbntqhOrFi" +COS_REGION = "ap-beijing" +COS_BUCKET = "static-1317843270" +COS_DOWNLOAD_DOMAIN = "static.valavala.com" + + +class CosUploader: + """腾讯云COS上传器""" + + def __init__(self, secret_id=None, secret_key=None, region=None, bucket=None, domain=None): + self.bucket = bucket or COS_BUCKET + self.domain = domain or COS_DOWNLOAD_DOMAIN + config = CosConfig( + Region=region or COS_REGION, + SecretId=secret_id or COS_SECRET_ID, + SecretKey=secret_key or COS_SECRET_KEY, + Scheme='https' + ) + self.client = CosS3Client(config) + + def upload(self, local_path: str, cos_key: str, content_type: str = None) -> str: + """ + 上传文件到COS + Args: + local_path: 本地文件路径 + cos_key: COS存储路径 + content_type: MIME类型,不指定则自动检测 + Returns: + 可访问的URL + """ + if not os.path.exists(local_path): + raise FileNotFoundError(f"文件不存在: {local_path}") + + if not content_type: + content_type = self._detect_content_type(local_path) + + kwargs = { + 'Bucket': self.bucket, + 'Key': cos_key, + 'LocalFilePath': local_path, + } + if content_type: + kwargs['ContentType'] = content_type + + self.client.upload_file(**kwargs) + return f"https://{self.domain}/{cos_key}" + + def upload_bytes(self, data: bytes, cos_key: str, content_type: str = None) -> str: + """上传字节数据到COS""" + kwargs = { + 'Bucket': self.bucket, + 'Key': cos_key, + 'Body': data, + } + if content_type: + kwargs['ContentType'] = content_type + self.client.put_object(**kwargs) + return f"https://{self.domain}/{cos_key}" + + def upload_batch(self, items: list) -> list: + """ + 批量上传 + Args: + items: [(local_path, cos_key), ...] 或 [(local_path, cos_key, content_type), ...] + Returns: + [(cos_key, url), ...] + """ + results = [] + for item in items: + local_path = item[0] + cos_key = item[1] + content_type = item[2] if len(item) > 2 else None + try: + url = self.upload(local_path, cos_key, content_type) + results.append((cos_key, url)) + except Exception as e: + print(f"[ERROR] 上传失败 {cos_key}: {e}", file=sys.stderr) + results.append((cos_key, None)) + return results + + def delete(self, cos_key: str): + """删除COS上的文件""" + self.client.delete_object(Bucket=self.bucket, Key=cos_key) + + def list_objects(self, prefix: str, max_keys: int = 100) -> list: + """列出COS上的文件""" + resp = self.client.list_objects(Bucket=self.bucket, Prefix=prefix, MaxKeys=max_keys) + return [item['Key'] for item in resp.get('Contents', []) if not item['Key'].endswith('/')] + + @staticmethod + def _detect_content_type(filepath: str) -> str: + mime, _ = mimetypes.guess_type(filepath) + return mime or 'application/octet-stream' + + +def main(): + parser = argparse.ArgumentParser(description='上传文件到腾讯云COS') + parser.add_argument('local_file', help='本地文件路径') + parser.add_argument('cos_key', help='COS存储路径') + parser.add_argument('--content-type', help='MIME类型(自动检测)', default=None) + args = parser.parse_args() + + uploader = CosUploader() + url = uploader.upload(args.local_file, args.cos_key, args.content_type) + print(url) + + +if __name__ == '__main__': + main()