From 723afdac3ddab45b4a9e216503ff0d402a653d41 Mon Sep 17 00:00:00 2001 From: OpenClaw Skill Sync Bot Date: Fri, 10 Apr 2026 19:57:16 +0800 Subject: [PATCH] auto-sync: tencent-cos-upload 2026-04-10_19:57 --- SKILL.md | 94 +++++++++++++ .../__pycache__/cos_upload.cpython-312.pyc | Bin 0 -> 6254 bytes scripts/cos_upload.py | 123 ++++++++++++++++++ 3 files changed, 217 insertions(+) create mode 100644 SKILL.md create mode 100644 scripts/__pycache__/cos_upload.cpython-312.pyc create mode 100644 scripts/cos_upload.py 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/__pycache__/cos_upload.cpython-312.pyc b/scripts/__pycache__/cos_upload.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed3f8f1c49be823191481999b3e474425be27d80 GIT binary patch literal 6254 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 uKq~g6N!h [--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()