chore: 初始化 123yunpan skill 仓库 (v1.3.0)

- 完整 skill 本体:SKILL.md / README.md / manifest.json / config.json
- scripts/123yunpan.py:零依赖 CLI(含空间双配额、QPS 限流、零依赖 XLSX)
- references/ 参考资料
- 排除 .workbuddy/ 开发目录与 __pycache__
This commit is contained in:
2026-09-27 22:41:43 +08:00
commit f0ecfa6de0
8 changed files with 5124 additions and 0 deletions
+746
View File
@@ -0,0 +1,746 @@
---
name: 123yunpan
description: "Use when interacting with 123 Cloud Drive (123云盘) via its open API. Supports file management, upload, download, sharing, offline download, direct links, image hosting, and video transcoding."
version: 1.3.0
author: 暖心向阳335
license: MIT
metadata:
hermes:
tags: [123yunpan, cloud-drive, file-management, api, storage]
related_skills: [bulk-downloading, resource-sharing-platform]
---
# 123云盘 (123 Cloud Drive) Skill
## Agent Quick Start
**脚本路径**: `scripts/123yunpan.py`(相对于本 skill 目录)
**首次使用**: 先跑 `config check` 确认凭证有效,再操作文件
**输出格式**: 加 `--json` 获取单行 JSON,方便解析
**文件 ID**: 所有操作都基于 `file_id`(数字),先 `file list` 或 `file search` 获取 ID
**⚠️ 操作规则**:
1. **删除规则**: 用户要求删除文件时,必须先向用户展示文件列表(如"删除文件如下:A.txt、B.zip"),确认后再执行删除操作,删除后提醒"如需找回,请前往回收站恢复"
2. **下载规则**: 下载完成后告知用户保存位置,如"文件已保存到:/path/to/file.pdf"
3. **分享规则**: 分享少于 10 个文件时直接给出链接;**超过 10 个时导出 Excel 文件并发送给用户**(阈值可用 `--excel-threshold` 覆盖,或用 `--no-excel` 关闭自动导出)
4. **搜索规则**: 用户未给出具体云盘位置只给了文件名,搜索到多个匹配结果时,列出文件让用户选择,不要自行决定
5. **批量规则**: 批量操作前先告知用户数量和预计耗时,如"共 150 个文件,预计需要 2 分钟"
6. **上传规则**: 用户未指定目标文件夹时,先列出根目录让用户选择上传位置
7. **错误规则**: 遇到错误时给出解决方案,不要只报错。如"文件不存在"→提示检查 file_id 是否正确;"分享失败"→提示检查文件是否违规
```
# 1. 验证配置(--json 是全局选项,必须写在子命令之前)
python3 scripts/123yunpan.py --json config check
# 2. 查看根目录
python3 scripts/123yunpan.py --json file list --parent-id 0
# 3. 搜索文件
python3 scripts/123yunpan.py --json file search "关键词" --parent-id 0
# 4. 查看目录树(快速了解结构)
python3 scripts/123yunpan.py file tree --parent-id 0 --max-depth 2
```
## Common Workflows
### 🔍 "帮我找某个文件"
```
1. file search "关键词" --parent-id 0 --json → 获取 file_id
2. file detail --file-id {id} --json → 确认文件信息
3. file download --file-id {id} -o ./ → 下载
4. 告知用户:文件已保存到:./xxx.pdf
```
### 🔗 "把这个文件夹分享出去"
```
1. file tree --parent-id {folder_id} --max-depth 2 → 确认内容
2. 少于 10 个:share create 逐个创建,直接给出链接
3. 超过 10 个:share create-batch --parent-id {folder_id} --expire 0 -o links.xlsx → 导出 Excel
```
### ⬆️ "上传文件到云盘"
```
1. 用户未指定位置 → file list --parent-id 0 --json → 列出根目录让用户选
2. upload --file /path/to/file --parent-id {folder_id}
3. 告知用户:文件已上传到:{folder_id}/xxx.pdf
```
### ⬇️ "批量下载这些文件"
```
1. file search "关键词" --parent-id {root} --json → 获取 file_id 列表
2. file download-batch --file-ids "id1,id2,id3" -o ./downloads/ → 批量下载
```
### 📊 "看看空间占用情况"
```
1. space → 专业空间 / 标准空间明细(双配额)
2. file storage-report --parent-id 0 --top 10 → 按文件夹分析+大文件排行
```
### 🗑️ "删除文件"
```
1. file list --parent-id {folder_id} --search "关键词" --json → 找到 file_id
2. 向用户展示将删除的文件列表,格式:删除文件如下:XXX.txt、XXX.zip
3. file trash --file-ids "{id1},{id2}" → 移入回收站
4. 提醒用户:如需找回,请前往回收站恢复
```
> ⚠️ **删除操作必须先向用户展示文件列表,确认后再执行!**
### 🔄 "把配置迁移到另一台机器"
```
# 机器 A:
config export -o config.json
# 机器 B:
config import --file config.json
config check → 验证导入成功
```
## Overview
This skill provides a complete CLI wrapper for the [123 Cloud Drive Open Platform API](https://123yunpan.yuque.com/org-wiki-123yunpan-muaork/cr6ced). It covers **70+ API endpoints** across authentication, file management, upload, download, sharing, offline download, direct links, image hosting, and video transcoding.
## Prerequisites
### 1. Purchase Developer Access Pack
Visit https://www.123pan.com/member and purchase a developer access pack. You will receive `client_id` and `client_secret` via in-app message.
### 2. Configuration
The skill requires three credentials stored in environment variables:
```bash
# Required for API authentication
export TWOPAN_CLIENT_ID="your_client_id_here"
export TWOPAN_CLIENT_SECRET="your_client_secret_here"
export TWOPAN_ACCESS_TOKEN="your_access_token_here"
```
**If these are not set when the skill is first used**, the CLI will output a clear reminder:
```
⚠️ 123云盘技能需要配置以下环境变量才能使用:
export TWOPAN_CLIENT_ID="your_client_id"
export TWOPAN_CLIENT_SECRET="your_client_secret"
export TWOPAN_ACCESS_TOKEN="your_access_token"
获取方式:
1. 访问 https://www.123pan.com/member 购买开发者权益包
2. 通过 client_id + client_secret 调用 /api/v1/access_token 获取 access_token
3. 将三个值设置为环境变量
或运行: 123yunpan.py setup
```
### 3. Interactive Setup (Recommended)
Run `123yunpan.py setup` without arguments for interactive credential entry:
```
$ python3 scripts/123yunpan.py setup
⚠️ 未检测到 client_id
获取方式:访问 https://www.123pan.com/member 购买开发者权益包
请输入 client_id: █
⚠️ 未检测到 client_secret
请输入 client_secret: █
正在获取 access_token...
✅ 配置已保存!
```
If config already exists (from a previous setup or env vars), it skips prompting and uses the existing values. Non-interactive mode also works: `setup --client-id ID --client-secret SECRET`.
## CLI Usage
```bash
# === Global Options ===
# Add --no-color to disable colored output, --json to output raw JSON
# Setup / get token
python3 scripts/123yunpan.py setup --client-id YOUR_ID --client-secret YOUR_SECRET
# Check config status
python3 scripts/123yunpan.py config
python3 scripts/123yunpan.py config check # 验证 token 有效性
python3 scripts/123yunpan.py config export --output config.json
python3 scripts/123yunpan.py config import --file config.json
# === File Management ===
python3 scripts/123yunpan.py file list --parent-id 0 --limit 100
python3 scripts/123yunpan.py file list --parent-id 0 --trashed # 只看回收站
python3 scripts/123yunpan.py file list --parent-id 0 --show-trashed # 含回收站
python3 scripts/123yunpan.py file search "Photoshop" --parent-id 27172609 --max-depth 3
python3 scripts/123yunpan.py file tree --parent-id 0 --max-depth 3
python3 scripts/123yunpan.py file storage-report --parent-id 0 --top 10
python3 scripts/123yunpan.py file detail --file-id 12345
python3 scripts/123yunpan.py file details-multi --file-ids "1,2,3"
python3 scripts/123yunpan.py file list --search "keyword"
python3 scripts/123yunpan.py file mkdir --name "New Folder" --parent-id 0
python3 scripts/123yunpan.py file rename --file-id 12345 --new-name "renamed.txt"
python3 scripts/123yunpan.py file rename-batch --file-ids "1,2,3" --new-names "a.txt,b.txt,c.txt"
python3 scripts/123yunpan.py file move --file-ids "1,2,3" --target-parent-id 0
python3 scripts/123yunpan.py file copy --file-id 12345 --target-parent-id 0
python3 scripts/123yunpan.py file copy-batch --file-ids "1,2,3" --target-parent-id 0
python3 scripts/123yunpan.py file copy-progress --task-id abc123
python3 scripts/123yunpan.py file trash --file-ids "12345"
python3 scripts/123yunpan.py file recover --file-id 12345
python3 scripts/123yunpan.py file recover-by-path --file-id 12345 --target-parent-id 0 --filename "restored.txt"
python3 scripts/123yunpan.py file download --file-id 12345 --output /path/to/save
python3 scripts/123yunpan.py file download-batch --file-ids "1,2,3" --output-dir /path/to/save
# === Upload (V2 Recommended) ===
python3 scripts/123yunpan.py upload --file /path/to/file.txt --parent-id 0
# === Space / User ===
python3 scripts/123yunpan.py space # 空间占用报告(专业 + 标准,双配额)
python3 scripts/123yunpan.py --json space # 机器可读版本
python3 scripts/123yunpan.py user-info
# === Share Management ===
python3 scripts/123yunpan.py share create --name "My Share" --file-ids "1,2,3" --expire 7
# 超过 10 个链接自动生成 Excel(三列:文件名称 / 链接 / 路径)
python3 scripts/123yunpan.py share create-batch --parent-id 27172609 --expire 0 -o result.xlsx
# 覆盖自动导出阈值 / 关闭自动导出 / 断点续跑
python3 scripts/123yunpan.py share create-batch --parent-id 27172609 --excel-threshold 50
python3 scripts/123yunpan.py share create-batch --parent-id 27172609 --no-excel
python3 scripts/123yunpan.py share create-batch --parent-id 27172609 --resume
python3 scripts/123yunpan.py share list --limit 10
python3 scripts/123yunpan.py share list --all-pages
python3 scripts/123yunpan.py share stats --top 10
# 注意:--share-ids / --file-ids 只接受单个值,多个 ID 必须写成一个逗号分隔的字符串
python3 scripts/123yunpan.py share delete-batch --share-ids "1,2,3"
python3 scripts/123yunpan.py share delete-batch --all -y # 删除全部(跳过确认)
python3 scripts/123yunpan.py share update --share-ids "1,2" --traffic-switch 2
python3 scripts/123yunpan.py share export --output shares.xlsx
python3 scripts/123yunpan.py share export --output shares.json # 按扩展名自动判断格式
python3 scripts/123yunpan.py share pay-create --name "Paid Share" --file-ids "1,2" --amount 10
python3 scripts/123yunpan.py share pay-list --limit 10
python3 scripts/123yunpan.py share pay-update --share-ids "1" --traffic-switch 1
# === Offline Download ===
python3 scripts/123yunpan.py offline create --url "https://example.com/file.zip" --parent-id 0
python3 scripts/123yunpan.py offline progress --task-id abc123
python3 scripts/123yunpan.py offline list
# === Direct Link ===
python3 scripts/123yunpan.py direct-link enable --file-id 12345
python3 scripts/123yunpan.py direct-link disable --file-id 12345
python3 scripts/123yunpan.py direct-link url --file-id 12345
python3 scripts/123yunpan.py direct-link short-url --file-id 12345 # 短直链(更隐私)
python3 scripts/123yunpan.py direct-link cache-refresh --file-id 12345
python3 scripts/123yunpan.py direct-link ip-list
python3 scripts/123yunpan.py direct-link ip-switch --switch 1
python3 scripts/123yunpan.py direct-link ip-update --ips "1.1.1.1,2.2.2.2"
python3 scripts/123yunpan.py direct-link offline-logs
python3 scripts/123yunpan.py direct-link traffic-logs
# === Image Hosting (OSS) ===
python3 scripts/123yunpan.py oss list --parent-id 0 --limit 100
python3 scripts/123yunpan.py oss detail --file-id 12345
python3 scripts/123yunpan.py oss delete --file-id 12345
python3 scripts/123yunpan.py oss move --file-id 12345 --target-parent-id 0
python3 scripts/123yunpan.py oss copy --file-ids "1,2,3" --target-parent-id 0
python3 scripts/123yunpan.py oss copy-process --task-id abc123
python3 scripts/123yunpan.py oss copy-fail --task-id abc123
# === Video Transcode ===
# 注意:transcode 系列用 --parent-file-id,不是 --parent-id
python3 scripts/123yunpan.py transcode list --parent-file-id 0 --limit 100
python3 scripts/123yunpan.py transcode folder --parent-file-id 0
python3 scripts/123yunpan.py transcode resolutions --file-id 12345
python3 scripts/123yunpan.py transcode transcode --file-id 12345 --resolution 1080p
python3 scripts/123yunpan.py transcode record --file-id 12345
python3 scripts/123yunpan.py transcode result --file-id 12345
python3 scripts/123yunpan.py transcode delete --file-id 12345
python3 scripts/123yunpan.py transcode download --file-id 12345 --output /path/
python3 scripts/123yunpan.py transcode download-m3u8 --file-id 12345 --output /path/
python3 scripts/123yunpan.py transcode download-all --file-id 12345 --output /path/
python3 scripts/123yunpan.py transcode upload-cloud --file-id 12345 --parent-id 0
# === Safebox ===
python3 scripts/123yunpan.py safebox unlock --password "your_password"
# === Cache Management ===
python3 scripts/123yunpan.py cache list # 列出所有缓存文件
python3 scripts/123yunpan.py cache clear # 清除全部缓存
python3 scripts/123yunpan.py cache clear --root-id 12345 # 清除指定文件夹的缓存
python3 scripts/123yunpan.py cache info # 显示缓存目录信息
```
## JSON Output Fields (`--json`)
加 `--json` 后所有命令输出单行 JSON,以下是主要命令的字段说明:
### `file list --json`
```json
{"code":0, "data":{"fileList":[
{"fileId":12345, "filename":"报告.pdf", "size":1048576, "type":0,
"parentFileID":0, "createAt":"2026-01-01T00:00:00Z", "trashed":0}
], "lastFileId":12345}}
```
- `type`: 0=文件, 1=文件夹
- `trashed`: 0=正常, 1=已删除
- `size`: 字节数
### `file detail --json`
```json
{"fileId":12345, "filename":"报告.pdf", "size":1048576,
"md5":"abc123...", "type":0, "parentFileID":0,
"downloadUrl":"https://...", "createAt":"2026-01-01T00:00:00Z"}
```
### `space --json`
```json
{"nickname": "暖心向阳335",
"total_quota": 21474836480, "total_used": 9223372036854775807,
"professional": {"used": 1.5e10, "quota": 2.0e10, "remain_days": 0,
"permanent": true, "used_pct": 75.0},
"standard": {"used": 1.2e9, "quota": 5.0e9, "remain_days": 0,
"permanent": true, "used_pct": 24.0}}
```
- `professional` 满 → 开放平台上传 / 离线下载 / 转码入口被阻断
- `remain_days` > 0 表示官方给的清理倒计时,超出后配额不可恢复
### `user-info --json`
```json
{"uid":1812749510, "nickname":"暖心向阳335",
"spaceUsed":9223372036854775807, "spacePermanent":9223372036854775807,
"vip":1}
```
- `spaceUsed`/`spacePermanent`: 字节数,用 `human_size()` 转换
### `share create --json`
```json
{"code":0, "data":{"shareID":5193007, "shareKey":"JyAKVv-o6bI"},
"_shareUrl":"https://www.123pan.com/s/JyAKVv-o6bI"}
```
- `_shareUrl` 是 CLI 自动拼接的,不是 API 返回的
### `share list --json`
```json
{"code":0, "data":{"shareList":[
{"shareId":5193007, "shareName":"分享",
"shareKey":"JyAKVv-o6bI", "shareExpire":7,
"previewCount":10, "downloadCount":5, "saveCount":2,
"createAt":"2026-01-01T00:00:00Z"}
], "lastShareId":5193007}}
```
### `upload --json`
```json
{"code":0, "data":{"fileID":12345, "completed":true, "reuse":false}}
```
- `reuse`: true=秒传成功(文件已存在)
### 通用规则
- 所有 API 返回格式: `{"code":0, "message":"ok", "data":{...}}`
- `code=0` 表示成功,非 0 为错误
- `file list` 和 `share list` 使用 `lastFileId`/`lastShareId` 做游标分页
- 错误时返回: `{"code":错误码, "message":"错误描述"}`
## API Reference Summary
| Category | Endpoint | Method |
|----------|----------|--------|
| Cache | cache list | CLI |
| Cache | cache clear | CLI |
| Cache | cache info | CLI |
| Auth | `/api/v1/access_token` | POST |
| Auth (OAuth2) | `/api/v1/oauth2/access_token` | POST |
| File List (v2) | `/api/v2/file/list` | GET |
| File List (v1) | `/api/v1/file/list` | GET |
| File Detail | `/api/v1/file/detail` | GET |
| File Details (multi) | `/api/v1/file/infos` | POST |
| Create Dir | `/upload/v1/file/mkdir` | POST |
| Rename (single) | `/api/v1/file/name` | PUT |
| Rename (batch) | `/api/v1/file/rename` | POST |
| Move | `/api/v1/file/move` | POST |
| Copy (single) | `/api/v1/file/copy` | POST |
| Copy (batch) | `/api/v1/file/async/copy` | POST |
| Copy Progress | `/api/v1/file/async/copy/process` | GET |
| Trash | `/api/v1/file/trash` | POST |
| Recover | `/api/v1/file/recover` | POST |
| Recover (by path) | `/api/v1/file/recover/by_path` | POST |
| Download Info | `/api/v1/file/download_info` | GET |
| Upload V1 | `/upload/v1/file/create` | POST |
| Upload V2 | `/upload/v2/file/create` | POST |
| Upload Slice | `/upload/v2/file/slice` | POST |
| Upload Complete | `/upload/v2/file/upload_complete` | POST |
| Upload Domain | `/upload/v2/file/domain` | GET |
| SHA1 Reuse | `/upload/v2/file/sha1_reuse` | POST |
| Single Upload | `/upload/v2/file/single/create` | POST |
| Share Create | `/api/v1/share/create` | POST |
| Share List | `/api/v1/share/list` | GET |
| Share Delete | `/api/v1/share/delete` | POST |
| Share Update | `/api/v1/share/list/info` | PUT |
| Pay Share Create | `/api/v1/share/content-payment/create` | POST |
| Pay Share List | `/api/v1/share/payment/list` | GET |
| Pay Share Update | `/api/v1/share/list/payment/info` | PUT |
| Offline Download | `/api/v1/offline/download` | POST |
| Offline Progress | `/api/v1/offline/download/process` | GET |
| User Info | `/api/v1/user/info` | GET |
| Direct Link Enable | `/api/v1/direct-link/enable` | POST |
| Direct Link Disable | `/api/v1/direct-link/disable` | POST |
| Direct Link URL | `/api/v1/direct-link/url` | GET |
| Direct Link Cache | `/api/v1/direct-link/cache/refresh` | POST |
| IP Blacklist Switch | `/api/v1/developer/config/forbide-ip/switch` | POST |
| IP Blacklist Update | `/api/v1/developer/config/forbide-ip/update` | POST |
| IP Blacklist List | `/api/v1/developer/config/forbide-ip/list` | GET |
| Direct Link Logs | `/api/v1/direct-link/offline/logs` | GET |
| Direct Link Traffic | `/api/v1/direct-link/log` | GET |
| Image Upload Flow | `/upload/v1/oss/file/mkdir` | POST |
| Image Create Dir | `/upload/v1/oss/file/mkdir` | POST |
| Image Create | `/upload/v1/oss/file/create` | POST |
| Image Upload URL | `/upload/v1/oss/file/get_upload_url` | POST |
| Image Upload Done | `/upload/v1/oss/file/upload_complete` | POST |
| Image Upload Result | `/upload/v1/oss/file/upload_async_result` | POST |
| Image Copy | `/api/v1/oss/source/copy` | POST |
| Image Copy Process | `/api/v1/oss/source/copy/process` | GET |
| Image Copy Fail | `/api/v1/oss/source/copy/fail` | GET |
| Image Move | `/api/v1/oss/file/move` | POST |
| Image Delete | `/api/v1/oss/file/delete` | POST |
| Image Detail | `/api/v1/oss/file/detail` | GET |
| Image List | `/api/v1/oss/file/list` | POST |
| Image Offline | `/api/v1/oss/offline/download` | POST |
| Image Offline Process | `/api/v1/oss/offline/download/process` | GET |
| Video Upload Cloud | `/api/v1/transcode/upload/from_cloud_disk` | POST |
| Video Folder Info | `/api/v1/transcode/folder/info` | POST |
| Video Resolutions | `/api/v1/transcode/video/resolutions` | POST |
| Video Transcode List | `/api/v1/video/transcode/list` | GET |
| Video Transcode | `/api/v1/transcode/video` | POST |
| Video Record | `/api/v1/transcode/video/record` | POST |
| Video Result | `/api/v1/transcode/video/result` | POST |
| Video Delete | `/api/v1/transcode/delete` | POST |
| Video Download | `/api/v1/transcode/file/download` | POST |
| Video M3U8 Download | `/api/v1/transcode/m3u8_ts/download` | POST |
| Video All Download | `/api/v1/transcode/file/download/all` | POST |
| Safebox Unlock | `/api/v1/safebox/id` | GET |
## Short Direct Link (短直链)
除了分享链接和完整直链外,还可以生成**短直链** — 格式为 `https://{user_id}.v.123pan.cn/{user_id}/{file_id}`,不暴露文件名,更隐私友好。
### 生成短直链
```bash
# CLI 命令
python3 scripts/123yunpan.py direct-link short-url --file-id 12345
# 输出示例
{
"short_url": "https://1812749510.v.123pan.cn/1812749510/12345",
"user_id": 1812749510,
"file_id": 12345
}
```
### 手动拼接(Python)
```python
import base64, json
def get_user_id_from_token(token):
"""从 JWT token 解析 user_id,无需额外 API 调用"""
parts = token.split(".")
payload = parts[1] + "=" * (4 - len(parts[1]) % 4)
return json.loads(base64.b64decode(payload)).get("id", 0)
user_id = get_user_id_from_token(access_token)
short_url = f"https://{user_id}.v.123pan.cn/{user_id}/{file_id}"
```
### 三种链接格式对比
| 格式 | 示例 | 特点 |
|------|------|------|
| 分享链接 | `https://www.123pan.com/s/JyAKVv-o6bI` | 需要登录/跳转,最常用 |
| 完整直链 | `https://xxx.v.123pan.cn/xxx/xxx/filename.zip` | 直接下载,暴露文件名 |
| 短直链 | `https://1812749510.v.123pan.cn/1812749510/12345` | 直接下载,不暴露文件名 |
> ⚠️ 短直链需要文件已在开启了直链功能的文件夹中,否则无法使用。
## Share URL Construction
The share create API returns `shareID` and `shareKey` — it does **NOT** return a full `shareUrl`. You must construct the URL yourself. The CLI supports two formats via `--url-format`:
| Format | CLI 参数 | URL 模式 | 示例 |
|--------|----------|----------|------|
| `short` (默认) | `--url-format short` | `https://www.123pan.com/s/{shareKey}` | `https://www.123pan.com/s/JyAKVv-o6bI` |
| `uid` | `--url-format uid` | `https://{user_id}.share.123pan.cn/123pan/{shareKey}` | `https://666666666.share.123pan.cn/123pan/JyAKVv-o6bI` |
`uid` 格式自动从 JWT token 解析 `user_id`,无需额外 API 调用。解析失败时自动回退到 `short`。
```bash
# 默认 short 格式
python3 scripts/123yunpan.py share create --name "分享" --file-ids 123,456 --expire 7
# UID 格式
python3 scripts/123yunpan.py share create --name "分享" --file-ids 123,456 --url-format uid
# 批量分享也支持
python3 scripts/123yunpan.py share create-batch --parent-id 27172609 --url-format uid -o result.xlsx
```
Example response from `POST /api/v1/share/create`:
```json
{
"code": 0,
"message": "ok",
"data": {
"shareID": 5193007,
"shareKey": "JyAKVv-o6bI"
}
}
```
## Direct Link & Short Direct Link
The skill supports two types of direct links:
**Full direct link** (contains filename in URL):
```
https://{user_id}.v.123pan.cn/{user_id}/{file_id}/{filename}
```
**Short direct link** (privacy-friendlier, no filename):
```
https://{user_id}.v.123pan.cn/{user_id}/{file_id}
```
To generate short direct links, parse `user_id` from the JWT access token (no extra API call needed):
```python
import base64, json
parts = token.split(".")
payload = parts[1] + "=" * (4 - len(parts[1]) % 4)
user_id = json.loads(base64.b64decode(payload))["id"]
short_link = f"https://{user_id}.v.123pan.cn/{user_id}/{file_id}"
```
> See `references/competitor-upload-patterns.md` for full upload workflow, SHA1 reuse, and WebDAV patterns.
## 空间规则:专业空间 / 标准空间(双配额)
根据官方 2026-09-24 公告,账号空间已拆分为**两套独立配额**,各有各的容量池与剩余天数:
| 空间类型 | 存放内容 | 与开放平台的关系 |
|----------|----------|------------------|
| **专业空间** | 本人持有的文件;≥10GB 文件;**直链**文件;转码文件;图床文件 | **任一项超额都会阻断开放平台的上传 / 离线下载 / 转码入口** |
| **标准空间** | 转存(保存)的文件,<10GB;重复文件与回收站文件**不计入**容量 | 超额同样阻断上传 |
用 `space` 命令查看明细,CLI 会自动做两件事:
1. **使用率 ≥ 90%** → 红色预警,提示随时可能出现写入失败
2. **专业空间剩余天数 > 0**(官方给定的清理倒计时,超出后配额不可恢复)→ 明确打印倒计时天数并标红
```bash
python3 scripts/123yunpan.py --json space # 机器可读:professional / standard / total 三段
```
`config check` 与 `upload` 命令在执行前都会做空间预检,专业空间已满时会直接给出阻断提示,避免上传中途才失败。
> ⚠️ **排查要点**:上传失败、离线下载任务创建失败,先看是**专业空间**还是**标准空间**超限 —— 两者是独立的,不能互相顶替。
## 分享链接 Excel 导出(三列)
`share create-batch` 在**有效链接数 > 阈值(默认 10)**时会自动生成 Excel 文件,列结构固定为三列:
| 列名 | 含义 | 示例 |
|------|------|------|
| **文件名称** | 不含分类前缀、不含扩展名的干净文件名 | `Cinema 4D R20`(原 `Cinema 4D/Cinema 4D R20.zip`) |
| **链接** | 该文件的分享/直链 URL,单元格为可点击超链接 | `https://www.123pan.com/s/JyAKVv-o6bI` |
| **路径** | 文件在云盘内的完整相对路径(含文件名与扩展名) | `软件/office/离线安装包/2021/ProPlus2021Retail.img` |
名称解析规则(`split_name_and_path()`):
1. 去掉扩展名 → `Cinema 4D R20.zip` → `Cinema 4D R20`
2. 若去掉扩展名后的名字以「父目录名」开头,则补回父目录名避免歧义
→ `Cinema 4D/Cinema 4D R20.zip` → `Cinema 4D R20`(而不是 ` R20`)
阈值控制:
```bash
--excel-threshold N # 覆盖默认阈值(默认 10)
--no-excel # 关闭自动导出,超过阈值也只在终端打印链接
--resume # 断点续跑:读取 ~/.123yunpan_cache/share_batch_{root_id}.json,只处理未完成的文件
```
> ✅ **零依赖**:xlsx 由 `zipfile` + 手写 OOXML XML 直接生成,不再需要 `openpyxl`。任何 Python 3 标准库环境都能出文件,不会出现"静默降级成 JSON"的情况。
## 限流与重试(QPS 保护)
官方开发须知明确给出各端点 QPS 上限,CLI 内置 `QPS_RULES` 表自动做节奏控制:
| 端点 | QPS |
|------|-----|
| `/api/v2/file/list` | **1.5(最严格)** |
| `/api/v1/user/info` | 10 |
| `/api/v1/access_token` | 10 |
| `/api/v1/share/create` | 3 |
| 其他 | 默认 1.39(保守兜底) |
配套行为:
- **自动节流**:每个端点按自己的 QPS 计算最小请求间隔,超限时 sleep 等待
- **指数退避重试**:HTTP 429 或 5xx → `2^attempt` 秒后重试,最多 4 次
- **401 自动刷新 Token**:带 `client_id/secret` 时自动刷新后重试一次
- **审核驳回文件过滤**:`status > 100` 的文件无法分享,遍历阶段直接剔除,避免逐个试错
## Recursive File Listing
The `file list` API only returns **direct children** of a folder. To find all files in a multi-level directory tree, you must traverse recursively. See `references/bulk-operations.md` for BFS and layered traversal patterns.
**Quick pattern — find all files under a root folder:**
```python
from collections import deque
def find_all_files(root_id, list_folder_fn):
queue = deque([(root_id, [])])
all_files = []
while queue:
pid, path = queue.popleft()
for item in list_folder_fn(pid):
if item.get("trashed"):
continue
if item["type"] == 1: # folder
queue.append((item["fileId"], path + [item["filename"]]))
else: # file
all_files.append({
"fileId": item["fileId"],
"filename": item["filename"],
"path": "/".join(path),
})
return all_files
```
> ⚠️ Deep traversal can involve hundreds of API calls. For 100+ folders, expect 5-10 minutes. Use checkpoint/resume (save progress to JSON every N items) to handle interruptions.
## Common Pitfalls
1. **API response format** (CRITICAL): The 123yunpan API returns `{code: 0, message: "ok", data: {...}}` — NOT `{result: {...}}`. Always parse the `data` field. Use `client._extract(resp)` — the CLI script handles both formats.
2. **Token expiry & auto-refresh**: access_token expires after a period. The CLI automatically refreshes on 401 responses using client_id/secret. For manual refresh, run `setup` or call `/api/v1/access_token`.
3. **File name restrictions**: Filenames must be <256 chars and cannot contain: `\\\\\\\"/:*?|><`
4. **Upload size**: Single file upload limit is 10GB via the API.
5. **`fileIDList` must be a string** (CRITICAL): When calling the share create API directly, `fileIDList` must be a **comma-separated string** (`"123,456"`), NOT a JSON array (`[123, 456]`). Passing an array causes the API to return "文件列表不能为空". The CLI handles this internally via `",".join(str(x) for x in file_ids)`. Max 100 file IDs per share. `shareExpire` must be 0, 1, 7, or 30.
6. **Share response has no `shareUrl`**: The share create response only returns `shareID` + `shareKey`. You must construct the URL. Use `--url-format short` (default, `https://www.123pan.com/s/{shareKey}`) or `--url-format uid` (`https://{user_id}.share.123pan.cn/123pan/{shareKey}`). See "Share URL Construction" section above.
7. **Pagination**: Use `lastFileId`/`lastShareId` for cursor-based pagination. When value is `-1`, you've reached the last page.
8. **Upload API domain routing** (CRITICAL): Upload endpoints are split across **two domains**:
- Main API domain (`https://open-api.123pan.com`): `/upload/v2/file/create` (chunked create), `/upload/v1/file/create` (v1 create)
- Upload domain (`https://openapi-upload.123242.com`): `/upload/v2/file/single/create` (single upload), `/upload/v2/file/slice` (chunked slice), `/upload/v2/file/upload_complete` (chunked complete)
- Upload domain is obtained via **GET** `/upload/v2/file/domain` — it returns a **list** `["https://..."]`, not an object with `host` field.
9. **Single upload requires multipart/form-data** (CRITICAL): The `/upload/v2/file/single/create` endpoint does NOT accept JSON body. It requires `Content-Type: multipart/form-data` with fields `parentFileID`, `filename`, `etag`, `size` as text parts and the file content as a file part. Sending JSON results in a 500 server crash.
10. **`duplicate` parameter** (CRITICAL): The `duplicate` field in upload create causes "duplicate参数错误" when set to `0` (skip). **Do not send `duplicate` at all** (defaults to overwrite), or use `2` (overwrite) or `1` (rename). The value `0` is rejected by the API. The CLI now defaults to not sending this parameter (overwrite behavior).
11. **Trash behavior**: The v2 file list includes trashed files — filter by `trashed` field yourself. `trashed: 1` means the file is in trash.
12. **Batch operations rate limit**: The CLI now enforces per-endpoint QPS automatically via the built-in `QPS_RULES` table (see "限流与重试" section) — do NOT add manual `time.sleep()` on top of it, or you'll halve the throughput. For very long batches (2500+ files) still use checkpoint/resume: `--resume` reads `~/.123yunpan_cache/share_batch_{root_id}.json` and skips already-done files.
13. **Deep directory traversal**: Folders can be 4-5 levels deep. Use BFS with a queue for unknown structures, or layered traversal when structure is known. Each level requires a separate API call — 1400+ folders may take 10+ minutes. **Use BFS caching** (`~/.123yunpan_cache/`) to avoid repeated traversals — 57s → 0.15s on cache hit. CLI commands `file search` and `share create-batch` auto-use cache; add `--no-cache` to force refresh.
14. **Common error responses**:
| `code` | `message` | Meaning |
|--------|-----------|---------|
| `code` | HTTP | `message` | Meaning / 处理建议 |
|--------|------|-----------|--------------------|
| 0 | 200 | ok | Success |
| 1 | 200 | 文件列表不能为空 | `fileIDList` is empty or wrong format (must be comma-separated string) |
| 1 | 200 | duplicate参数错误 | `duplicate` field value is invalid (don't send `0`; use absent/`1`/`2`) |
| 1 | 200 | 分片MD5不能为空 | Slice MD5 missing — check field name matches API expectation |
| 1 | 200 | 该目录下文件名重复无法创建 | Filename already exists in target directory (use `--duplicate 2` to overwrite) |
| 2 | 200 | 分享ID非法 | File ID doesn't exist or has no share permission |
| 10 | 200 | 文件名已存在 | Target folder already has a file with that name |
| 16 | 200 | 文件不存在 | File ID not found |
| **20** | 200 | 分享文件涉嫌违规,禁止分享 | File content flagged by platform policy (e.g. `.alist` files) — CLI now filters `status > 100` files before sharing |
| **401** | 401 | — | access_token 无效或已过期 → 自动刷新重试一次;仍失败则 `setup` 重新获取 |
| **429** | 429 | — | 触发平台限流 → CLI 指数退避自动重试(最多 4 次),无需手动干预 |
| **5066** | 200 | — | 文件不存在(开放平台语义),检查 fileId |
| **5113** | 200 | — | 自用下载流量不足 → 升级 VIP 可获得无限流量下载 |
> CLI 内置 `ERROR_HINTS` 表,以上错误码会以中文人话 + 处置建议的形式打印,不会只丢一个裸错误码。
15. **审核驳回文件无法分享 (CRITICAL)**: Files with `status > 100` are rejected by content review and can never be shared. `share create-batch` filters them out during traversal instead of failing one-by-one. If a file you expect to share silently disappears from the result, check its `status` field first.
16. **专业空间 / 标准空间是两套独立配额 (CRITICAL)**: Per the 2026-09-24 announcement, exceeding **either** blocks open-platform uploads / offline downloads / transcoding. Standard space does **not** count duplicate files or trash files. Run `space` before a large upload to see which pool is actually full.
17. **Excel 导出是三列结构,不是「文件名 + 链接」**: Columns are fixed as 文件名称 / 链接 / 路径. The name column strips the folder prefix and extension (`Cinema 4D/Cinema 4D R20.zip` → `Cinema 4D R20`), while 路径 keeps the full relative path with filename (`软件/office/离线安装包/2021/ProPlus2021Retail.img`). Don't re-derive these columns downstream — read them as-is.
18. **Excel 导出零依赖,不要再去装 openpyxl**: xlsx is generated by hand via `zipfile` + OOXML XML (`write_xlsx()`). There is no `import openpyxl` anywhere in the script. Previously a missing openpyxl would silently degrade export to JSON — that can no longer happen.
19. **Packaging & versioning**: Version must be updated in **two places**: (1) `scripts/123yunpan.py` `VERSION = "X.Y.Z"`, (2) `SKILL.md` frontmatter (`version: X.Y.Z`), plus (3) `manifest.json` (`version`). No `zip`/`unzip` CLI — use Python's `zipfile` module. Package includes SKILL.md + README.md + scripts/ + references/ (no LICENSE per user preference). Exclude `__pycache__/`. Extraction for patching: `python3 -c "import zipfile; zipfile.ZipFile('pkg.zip').extractall('_tmp')"`. Repackaging: `cd _tmp && python3 -c "import zipfile,os; [zipfile.ZipFile('out.zip','w',zipfile.ZIP_DEFLATED).write(f, os.path.relpath(f,'.')) for r,d,fs in os.walk('.') for f in fs if '__pycache__' not in r]"`.
> ⚠️ Remember to also update the version in `scripts/123yunpan.py` `VERSION` when bumping the version in SKILL.md frontmatter and manifest.json.
20. **Argparse parameter names must match SKILL.md exactly**: Before adding/changing argparse arguments, cross-check every `--flag` in SKILL.md CLI examples. Mismatches (e.g. `--name` in code vs `--new-name` in docs) silently break user workflows. Use `parse_int_list()` (not `type=int, nargs="+"`) for all file-ID arguments to support comma-separated input (`--file-ids "1,2,3"`).
- ⚠️ `--file-ids` / `--share-ids` / `--name` **只接受一个值**。多 ID 必须写成**一个带引号的逗号分隔字符串** `--file-ids "1,2,3"`;写成 `--file-ids 1 2 3` 会报 `unrecognized arguments: 2 3`,因为 `1 2 3` 会被当成多余的位置参数。
- ⚠️ `--json` / `--no-color` 是**全局选项**,必须写在子命令**之前**(`--json config check`),写在后面报 `unrecognized arguments: --json`。
- ⚠️ `transcode` 系列用 `--parent-file-id`,与 `file` 系列的 `--parent-id` **不是同一个参数**。
- 可用 `.workbuddy/verify_doc_flags.py` 一键回归:它对文档里每一条 CLI 示例跑一遍 argparse 解析,找出「文档写了但代码没有」的选择器。
21. **`_refresh_token` uses imported names, not module paths**: The `ApiClient` methods use `Request` and `urlopen` (from `from urllib.request import ...`), NOT `urllib.request.Request`. Using the module path will crash with `NameError` since `urllib` is not imported as a module.
22. **BFS traversal optional params**: `bfs_list_files()` accepts `max_depth`, `use_cache`, `max_age` — always forward these from callers (e.g. `cmd_file_search`). Unused parameters silently cause unbounded traversal.
23. **Global options placement**: `--no-color` and `--json` must appear **before** the subcommand (e.g., `--json file list --parent-id 0`), not after. Placing them after the subcommand will cause argparse to ignore them.
24. **`file tree` vs recursive traversal**: `file tree` is a display-only convenience command that shows directory structure. For bulk operations (downloading, moving, sharing), use recursive BFS traversal or `file list` with pagination — `file tree` does not return file IDs suitable for batch operations.
25. **Share name is auto-truncated to 35 chars**: Paid shares require names < 35 characters; the CLI truncates automatically to avoid a 400 from the API. Don't pass a name longer than that expecting it round-trip.
26. **`share export` format is inferred from the file extension**: `--output shares.xlsx` → Excel, `--output shares.json` → JSON. There is **no `--format` flag** on `share export` (passing it yields `unrecognized arguments`). Both go through the same zero-dependency `write_xlsx()` / JSON path.
27. **`share pay-update` has no `--amount`**: Paid-share updates accept `--traffic-switch` / `--traffic-limit-switch` / `--traffic-limit` only. To change the price, recreate the share (or check the current API doc) rather than trying to patch `--amount`.
28. **`config import` safety**: Importing config will overwrite your current `~/.123yunpan_config.json`. Back up your existing config before importing. The CLI warns before overwriting if the file exists.
See `references/bulk-operations.md` for batch operation code patterns, BFS traversal, and background execution tips.
## Verification Checklist
- [ ] Environment variables `TWOPAN_CLIENT_ID`, `TWOPAN_CLIENT_SECRET`, `TWOPAN_ACCESS_TOKEN` are set
- [ ] Run `123yunpan.py setup` to validate credentials and fetch token
- [ ] Test with `123yunpan.py file list --parent-id 0 --limit 5`
- [ ] Confirm token refresh works by checking `~/.123yunpan_config.json`
- [ ] Run `123yunpan.py space` — two quotas (professional / standard) both reported
- [ ] Run `123yunpan.py share create-batch --parent-id {id}` with > 10 files → xlsx auto-generated, 3 columns correct
- [ ] Confirm no `openpyxl` dependency: `grep -c "import openpyxl" scripts/123yunpan.py` → `0`(脚本里只剩一句注释提到它)
## Changelog
### v1.3.0
- **适配新空间规则**: 新增 `space` 命令,按官方 2026-09-24 公告拆分为**专业空间 / 标准空间**双配额展示;使用率 ≥90% 红色预警;专业空间剩余天数倒计时提示;`config check` 与 `upload` 增加空间预检
- **改**: 分享链接 Excel 导出阈值 **20 → 10**(超过 10 个即自动生成 Excel),新增 `--excel-threshold` 覆盖与 `--no-excel` 关闭
- **改**: Excel 列结构改为**三列** —— 文件名称(去分类前缀、去扩展名)/ 链接(超链接)/ 路径(云盘内完整相对路径)
- **新增**: `--resume` 断点续跑,读取 `~/.123yunpan_cache/share_batch_{root_id}.json` 只处理未完成文件
- **重写**: Excel 写入器改为**零依赖**(`zipfile` + 手写 OOXML),彻底移除 `openpyxl` 依赖,不再有"缺库静默降级 JSON"的问题
- **重构**: API 客户端加入 `QPS_RULES` 端点级限流(`/api/v2/file/list` = 1.5 QPS 最严格)+ 429/5xx 指数退避重试(最多 4 次)+ 401 自动刷新 Token 重试一次
- **新增**: `ERROR_HINTS` 中文错误码映射(401 / 429 / 5066 / 5113 / 20 等),失败时给出原因与处置建议
- **修复**: 分享名称超 35 字符导致 API 400 → 主动截断;`status > 100`(审核驳回)文件在遍历阶段过滤,不再逐个试错
### v1.2.0 (2026-05-09)
- **新增**: `--no-color` 和 `--json` 全局选项,支持禁用彩色输出和 JSON 格式输出
- **新增**: `file tree` 命令 — 目录树展示,支持 `--parent-id` 和 `--max-depth` 参数
- **新增**: `file download-batch` 命令 — 批量下载文件,支持 `--file-ids` 和 `--output-dir` 参数
- **新增**: `file storage-report` 命令 — 存储分析,支持 `--parent-id` 和 `--top` 参数
- **新增**: `offline list` 命令 — 列出离线下载任务(占位,待完善)
- **新增**: `share export` 命令 — 导出分享记录到 Excel/JSON,支持 `--output` 和 `--format` 参数
- **新增**: `config export` 命令 — 导出配置到 JSON,支持 `--output` 参数
- **新增**: `config import` 命令 — 从 JSON 导入配置,支持 `--file` 参数
### v1.1.5 (2026-05-09)
- **修复**: `upload` 命令的 `--duplicate` 参数默认值从 `0`(跳过)改为不发送(默认覆盖),避免API报错
- **修复**: `_refresh_token` 方法使用了未导入的 `urllib.request` 模块,导致自动刷新Token时崩溃
- **修复**: `file search --max-depth` 参数未实际生效,BFS遍历无深度限制
- **修复**: 代码参数名与文档不一致(`--new-name`、`--target-parent-id`、`--ips`),统一到文档标准
- **优化**: 所有 `--file-ids`/`--share-ids` 参数同时支持逗号分隔和空格分隔格式(如 `1,2,3` 或 `1 2 3`)
- **新增**: `cache list` 命令 - 列出所有BFS缓存文件
- **新增**: `cache clear` 命令 - 清除缓存(支持指定文件夹或全部清除)
- **新增**: `cache info` 命令 - 显示缓存目录信息
- **优化**: 批量操作进度显示增加ETA(预计剩余时间)估算
- **优化**: 清理重复的import语句
### v1.1.4
- 初始版本,支持70+ API端点