feat(woozooo): 新增蓝奏云网盘支持

新增蓝奏云网盘适配,支持文件列表获取、重命名及域名注入;
更新相关文档、测试用例及多语言描述,将蓝奏云纳入支持平台。
这个提交包含在:
Zain 2026-08-15 10:37:34 +08:00
父节点 0f8e981589
当前提交 d32e5ef66c
共有 26 个文件被更改,包括 397 次插入 和 33 次删除

查看文件

@ -4,7 +4,7 @@
**一款覆盖多家主流网盘的文件批量重命名工具**
支持 115 网盘、123 云盘、阿里云盘、百度网盘、移动云盘、天翼云盘、夸克网盘、UC 网盘的智能化文件管理
支持 115 网盘、123 云盘、阿里云盘、百度网盘、蓝奏云、移动云盘、天翼云盘、迅雷云盘、夸克网盘、UC 网盘的智能化文件管理
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.3+-blue.svg)](https://www.typescriptlang.org/)
@ -36,8 +36,10 @@
- **123 云盘** - 支持文件批量重命名和页面同步
- **阿里云盘** - 支持文件批量重命名和页面同步
- **百度网盘** - 支持文件批量重命名和页面同步
- **蓝奏云** - 支持文件批量重命名和页面同步
- **移动云盘** - 支持文件批量重命名和页面同步
- **天翼云盘** - 支持文件批量重命名和页面同步
- **迅雷云盘** - 支持文件批量重命名和页面同步
- **夸克网盘** - 支持文件批量重命名和页面同步
- **UC 网盘** - 支持文件批量重命名和页面同步
@ -411,7 +413,9 @@ src/
│ ├── cmcc/ # 移动云盘适配器
│ ├── esurfing/ # 天翼云盘适配器
│ ├── quark/ # 夸克网盘适配器
│ └── uc/ # UC 网盘适配器
│ ├── uc/ # UC 网盘适配器
│ ├── xunlei/ # 迅雷云盘适配器
│ └── woozooo/ # 蓝奏云适配器
├── rules/ # 重命名规则
│ ├── base-rule.ts # 规则基类
│ ├── rule-factory.ts # 规则工厂
@ -817,16 +821,18 @@ A: 请在 GitHub Issues 中提交,包含:
### 当前版本 (v1.5.0) 🎉
**多平台版本正式发布!** 当前版本已覆盖 8 个网盘平台,并保留完整的规则引擎、预览、冲突检测、重试和页面同步能力:
**多平台版本正式发布!** 当前版本已覆盖 10 个网盘平台,并保留完整的规则引擎、预览、冲突检测、重试和页面同步能力:
#### 🚀 核心功能增强
- ✅ **8 个网盘平台支持**
- ✅ **10 个网盘平台支持**
- 115 网盘 - 支持文件批量重命名和页面同步
- 123 云盘 - 支持文件批量重命名和页面同步
- 阿里云盘 - 支持文件批量重命名和页面同步
- 百度网盘 - 支持文件批量重命名和页面同步
- 蓝奏云 - 支持文件批量重命名和页面同步
- 移动云盘 - 支持文件批量重命名和页面同步
- 天翼云盘 - 支持文件批量重命名和页面同步
- 迅雷云盘 - 支持文件批量重命名和页面同步
- 夸克网盘 - 支持文件批量重命名和页面同步
- UC 网盘 - 支持文件批量重命名和页面同步

查看文件

@ -4,7 +4,7 @@
"description": "Extension name"
},
"extDescription": {
"message": "Powerful batch renaming tool for Quark Drive, Aliyun Drive, and Baidu Cloud Drive",
"message": "Powerful batch renaming tool for Quark Drive, Aliyun Drive, Baidu Cloud Drive, 115 Drive, 123Pan, CMCC Drive, Esurfing Cloud, Xunlei Drive, Lanzou Cloud, and UC Drive",
"description": "Extension description"
},

查看文件

@ -4,7 +4,7 @@
"description": "扩展名称"
},
"extDescription": {
"message": "强大的批量重命名工具,支持夸克网盘、阿里云盘、百度网盘文件批量重命名",
"message": "强大的批量重命名工具,支持夸克网盘、阿里云盘、百度网盘、115网盘、123云盘、移动云盘、天翼云盘、迅雷云盘、蓝奏云、UC网盘文件批量重命名",
"description": "扩展描述"
},

查看文件

@ -4,7 +4,7 @@
"description": "擴充功能名稱"
},
"extDescription": {
"message": "強大的批次重新命名工具,支援夸克網盤、阿里雲盤、百度雲端硬碟檔案批次重新命名",
"message": "強大的批次重新命名工具,支援夸克網盤、阿里雲盤、百度雲端硬碟、115網盤、123雲盤、移動雲盤、天翼雲盤、迅雷雲盤、藍奏雲、UC網盤檔案批次重新命名",
"description": "擴充功能描述"
},

查看文件

@ -239,6 +239,9 @@ The Extension requests access to the following websites:
- `https://yun.139.com/*` - CMCC Drive pages
- `https://personal-kd-njs.yun.139.com/*` - CMCC Drive API
- `https://cloud.189.cn/*` - Esurfing Cloud pages and API
- `https://pan.xunlei.com/*` - Xunlei Drive pages
- `https://api-pan.xunlei.com/*` - Xunlei Drive API
- `https://pc.woozooo.com/*` - Lanzou Cloud pages and API
- `https://pan.quark.cn/*` - Quark Drive pages and API
- `https://drive.uc.cn/*` - UC Drive pages
- `https://pan.uc.cn/*` - UC Drive pages

查看文件

@ -90,7 +90,7 @@ cd ..
2. **Summary(简短描述)**
```
支持 115、123、阿里、百度、移动、天翼、夸克、UC 网盘的批量重命名工具
支持 115、123、阿里、百度、蓝奏、移动、天翼、迅雷、夸克、UC 网盘的批量重命名工具
```
3. **Category(分类)**
@ -217,7 +217,7 @@ cd ..
3. **Short description(简短描述)**
```
支持 115、123、阿里、百度、移动、天翼、夸克、UC 网盘的批量重命名工具
支持 115、123、阿里、百度、蓝奏、移动、天翼、迅雷、夸克、UC 网盘的批量重命名工具
```
4. **Long description(详细描述)**

查看文件

@ -7,8 +7,8 @@
- **英文**: CloudDrive Renamer
### 简短描述 (132字符以内)
- **中文**: 支持 115、123、阿里、百度、移动、天翼、夸克、UC 网盘的批量重命名工具
- **英文**: Batch rename files across 115 Drive, 123Pan, Aliyun, Baidu, CMCC, Esurfing, Quark, and UC Drive
- **中文**: 支持 115、123、阿里、百度、蓝奏、移动、天翼、迅雷、夸克、UC 网盘的批量重命名工具
- **英文**: Batch rename files across 115 Drive, 123Pan, Aliyun, Baidu, Lanzou, CMCC, Esurfing, Xunlei, Quark, and UC Drive
### 详细描述
@ -23,8 +23,10 @@
• 123 云盘 - 支持文件批量重命名
• 阿里云盘 - 支持文件批量重命名
• 百度网盘 - 支持文件批量重命名
• 蓝奏云 - 支持文件批量重命名
• 移动云盘 - 支持文件批量重命名
• 天翼云盘 - 支持文件批量重命名
• 迅雷云盘 - 支持文件批量重命名
• 夸克网盘 - 支持文件批量重命名
• UC 网盘 - 支持文件批量重命名
@ -73,8 +75,10 @@ A powerful batch renaming tool for cloud drives, designed to enhance file manage
• 123Pan - Batch rename support
• Aliyun Drive - Batch rename support
• Baidu Cloud Drive - Batch rename support
• Lanzou Cloud - Batch rename support
• CMCC Drive - Batch rename support
• Esurfing Cloud - Batch rename support
• Xunlei Drive - Batch rename support
• Quark Drive - Batch rename support
• UC Drive - Batch rename support
@ -155,6 +159,9 @@ This project is open-sourced under MIT License. Contributions and suggestions ar
- https://yun.139.com/* - 移动云盘页面
- https://personal-kd-njs.yun.139.com/* - 移动云盘 API
- https://cloud.189.cn/* - 天翼云盘页面与 API
- https://pan.xunlei.com/* - 迅雷云盘页面
- https://api-pan.xunlei.com/* - 迅雷云盘 API
- https://pc.woozooo.com/* - 蓝奏云页面与 API
- https://pan.quark.cn/* - 夸克网盘页面与 API
- https://drive.uc.cn/* - UC 网盘页面
- https://pan.uc.cn/* - UC 网盘页面

查看文件

@ -7,8 +7,8 @@
- **英文**: CloudDrive Renamer
### 简短描述 (不超过150字符)
- **中文**: 支持 115、123、阿里、百度、移动、天翼、夸克、UC 网盘的批量重命名工具
- **英文**: Batch rename files across 115 Drive, 123Pan, Aliyun, Baidu, CMCC, Esurfing, Quark, and UC Drive
- **中文**: 支持 115、123、阿里、百度、蓝奏、移动、天翼、迅雷、夸克、UC 网盘的批量重命名工具
- **英文**: Batch rename files across 115 Drive, 123Pan, Aliyun, Baidu, Lanzou, CMCC, Esurfing, Xunlei, Quark, and UC Drive
### 完整描述
@ -23,8 +23,10 @@
• 123 云盘 - 支持文件批量重命名
• 阿里云盘 - 支持文件批量重命名
• 百度网盘 - 支持文件批量重命名
• 蓝奏云 - 支持文件批量重命名
• 移动云盘 - 支持文件批量重命名
• 天翼云盘 - 支持文件批量重命名
• 迅雷云盘 - 支持文件批量重命名
• 夸克网盘 - 支持文件批量重命名
• UC 网盘 - 支持文件批量重命名
@ -96,8 +98,10 @@ A powerful batch renaming tool for cloud drives, designed to enhance file manage
• 123Pan - Batch rename support
• Aliyun Drive - Batch rename support
• Baidu Cloud Drive - Batch rename support
• Lanzou Cloud - Batch rename support
• CMCC Drive - Batch rename support
• Esurfing Cloud - Batch rename support
• Xunlei Drive - Batch rename support
• Quark Drive - Batch rename support
• UC Drive - Batch rename support
@ -169,12 +173,12 @@ Edge Add-ons允许添加搜索关键词以提高扩展的可发现性。
**推荐关键词(英文)**:
```
rename, batch rename, file rename, cloud drive, cloud storage, 115 drive, 123pan, aliyun drive, baidu cloud, quark drive, file management, bulk rename, batch operations, productivity
rename, batch rename, file rename, cloud drive, cloud storage, 115 drive, 123pan, aliyun drive, baidu cloud, lanzou cloud, xunlei drive, quark drive, file management, bulk rename, batch operations, productivity
```
**推荐关键词(中文)**:
```
重命名, 批量重命名, 文件重命名, 云盘, 网盘, 115网盘, 123云盘, 阿里云盘, 百度网盘, 移动云盘, 天翼云盘, 夸克网盘, UC网盘, 文件管理, 批量操作, 效率工具
重命名, 批量重命名, 文件重命名, 云盘, 网盘, 115网盘, 123云盘, 阿里云盘, 百度网盘, 蓝奏云, 移动云盘, 天翼云盘, 迅雷云盘, 夸克网盘, UC网盘, 文件管理, 批量操作, 效率工具
```
**关键词策略说明**:
@ -244,6 +248,9 @@ rename, batch rename, file rename, cloud drive, cloud storage, 115 drive, 123pan
| https://yun.139.com/* | 移动云盘 | 在移动云盘页面注入重命名工具界面 |
| https://personal-kd-njs.yun.139.com/* | 移动云盘 API | 调用移动云盘 API 执行文件列表读取和重命名 |
| https://cloud.189.cn/* | 天翼云盘 | 在天翼云盘页面注入界面并调用 API |
| https://pan.xunlei.com/* | 迅雷云盘 | 在迅雷云盘页面注入重命名工具界面 |
| https://api-pan.xunlei.com/* | 迅雷云盘 API | 调用迅雷云盘 API 执行文件列表读取和重命名 |
| https://pc.woozooo.com/* | 蓝奏云 | 在蓝奏云页面注入界面并调用 API |
| https://pan.quark.cn/* | 夸克网盘 | 在夸克网盘页面注入界面并调用 API |
| https://drive.uc.cn/* | UC 网盘 | 在 UC 网盘页面注入重命名工具界面 |
| https://pan.uc.cn/* | UC 网盘 | 支持 UC 网盘备用页面域名 |

查看文件

@ -35,7 +35,7 @@ rename, batch rename, file rename, cloud drive, 115 drive, 123pan, aliyun drive,
虽然Edge主要使用英文关键词,但如果支持中文商店,可以考虑:
```
重命名, 批量重命名, 文件重命名, 云盘, 网盘, 115网盘, 123云盘, 阿里云盘, 百度网盘, 移动云盘, 天翼云盘, 夸克网盘, UC网盘, 文件管理, 批量操作, 效率工具
重命名, 批量重命名, 文件重命名, 云盘, 网盘, 115网盘, 123云盘, 阿里云盘, 百度网盘, 蓝奏云, 移动云盘, 天翼云盘, 迅雷云盘, 夸克网盘, UC网盘, 文件管理, 批量操作, 效率工具
```
### 关键词策略
@ -399,7 +399,7 @@ rename, batch rename, file rename, cloud drive, 115 drive, 123pan, aliyun drive,
#### Short Description(简短描述)
```
支持 115、123、阿里、百度、移动、天翼、夸克、UC 网盘的批量重命名工具
支持 115、123、阿里、百度、蓝奏、移动、天翼、迅雷、夸克、UC 网盘的批量重命名工具
```
#### Long Description(详细描述)

查看文件

@ -24,7 +24,8 @@
"https://personal-kd-njs.yun.139.com/*",
"https://cloud.189.cn/*",
"https://pan.xunlei.com/*",
"https://api-pan.xunlei.com/*"
"https://api-pan.xunlei.com/*",
"https://pc.woozooo.com/*"
],
"background": {
"service_worker": "src/background/service-worker.ts",
@ -113,6 +114,15 @@
"src/content/index.ts"
],
"run_at": "document_idle"
},
{
"matches": [
"https://pc.woozooo.com/*"
],
"js": [
"src/content/index.ts"
],
"run_at": "document_idle"
}
],
"web_accessible_resources": [
@ -130,7 +140,8 @@
"src/adapters/123/page-script.js",
"src/adapters/cmcc/page-script.js",
"src/adapters/esurfing/page-script.js",
"src/adapters/xunlei/page-script.js"
"src/adapters/xunlei/page-script.js",
"src/adapters/woozooo/page-script.js"
],
"matches": [
"https://pan.quark.cn/*",
@ -143,7 +154,8 @@
"https://yun.123pan.cn/*",
"https://yun.139.com/*",
"https://cloud.189.cn/*",
"https://pan.xunlei.com/*"
"https://pan.xunlei.com/*",
"https://pc.woozooo.com/*"
]
}
],

查看文件

@ -1,7 +1,7 @@
{
"name": "cloud-drive-renamer",
"version": "1.5.0",
"description": "云盘文件批量重命名工具,支持 115 网盘、123 云盘、阿里云盘、百度网盘、移动云盘、天翼云盘、迅雷云盘、夸克网盘、UC 网盘",
"description": "云盘文件批量重命名工具,支持 115 网盘、123 云盘、阿里云盘、百度网盘、蓝奏云、移动云盘、天翼云盘、迅雷云盘、夸克网盘、UC 网盘",
"type": "module",
"scripts": {
"dev": "pnpm run sync:version && vite",
@ -33,6 +33,8 @@
"cmcc",
"esurfing",
"xunlei",
"woozooo",
"lanzou",
"quark",
"uc"
],

查看文件

@ -238,7 +238,7 @@ export abstract class SharedCloudDriveAdapter extends BasePlatformAdapter {
private applyRenameMappingToDomById(renameInfoById: Map<string, { oldName?: string; newName: string }>, root: ParentNode = document): number {
let patched = 0;
const nodes = Array.from(root.querySelectorAll('[data-file-id],[data-fid],[data-fileid],[data-row-key],[data-key],[data-id],[file_id]'));
const nodes = Array.from(root.querySelectorAll('[data-file-id],[data-fid],[data-fileid],[data-row-key],[data-key],[data-id],[file_id],.f_tb[id^="f"]'));
for (const node of nodes) {
const fileId =
@ -248,13 +248,14 @@ export abstract class SharedCloudDriveAdapter extends BasePlatformAdapter {
node.getAttribute('data-row-key') ||
node.getAttribute('data-key') ||
node.getAttribute('data-id') ||
node.getAttribute('file_id');
node.getAttribute('file_id') ||
node.id?.match(/^f(\d+)$/)?.[1];
if (!fileId) continue;
const info = renameInfoById.get(fileId);
if (!info) continue;
const row = node.closest('tr,[role="row"],.ant-table-row,.document_table_list,.file-item,.list-item,li[rel="item"],li') || node;
const row = node.closest('tr,[role="row"],.ant-table-row,.document_table_list,.f_tb,.file-item,.list-item,li[rel="item"],li') || node;
patched += this.patchRowElement(row, info.oldName, info.newName);
}
@ -265,7 +266,7 @@ export abstract class SharedCloudDriveAdapter extends BasePlatformAdapter {
if (renameByOldName.size === 0) return 0;
let patched = 0;
const nodes = Array.from(root.querySelectorAll('[class*="SourceListItem__name"],.touch-div,.document_table_list_name_text,.file-name,.filename,.name,.table-file-name,.table-file-name-tooltip-host,.table-list-file-name,[title],[aria-label]'));
const nodes = Array.from(root.querySelectorAll('[class*="SourceListItem__name"],.f_name_title,.touch-div,.document_table_list_name_text,.file-name,.filename,.name,.table-file-name,.table-file-name-tooltip-host,.table-list-file-name,[title],[aria-label]'));
for (const node of nodes) {
for (const [oldName, newName] of renameByOldName.entries()) {
patched += this.patchNameElement(node, oldName, newName);
@ -275,7 +276,7 @@ export abstract class SharedCloudDriveAdapter extends BasePlatformAdapter {
}
private patchRowElement(row: Element, oldName: string | undefined, newName: string): number {
const preferredNameNode = row.querySelector('[class*="SourceListItem__name"],.touch-div,.document_table_list_name_text,.table-file-name,.table-file-name-tooltip-host,.table-list-file-name,.file-name,.filename,.name');
const preferredNameNode = row.querySelector('[class*="SourceListItem__name"],.f_name_title,.touch-div,.document_table_list_name_text,.table-file-name,.table-file-name-tooltip-host,.table-list-file-name,.file-name,.filename,.name');
if (preferredNameNode && oldName) {
return this.patchStructuredNameWithinElement(preferredNameNode, oldName, newName);
}
@ -396,7 +397,7 @@ export abstract class SharedCloudDriveAdapter extends BasePlatformAdapter {
const roots = new Set<ParentNode>();
const ownerWindow = doc.defaultView ?? window;
const NodeCtor = ownerWindow.Node;
const rowSelector = 'tr,[role="row"],.ant-table-row,.document_table_list,.file-item,.list-item,li[rel="item"],li';
const rowSelector = 'tr,[role="row"],.ant-table-row,.document_table_list,.f_tb,.file-item,.list-item,li[rel="item"],li';
const addNode = (node: Node | null) => {
if (!node) return;
@ -520,7 +521,7 @@ export abstract class SharedCloudDriveAdapter extends BasePlatformAdapter {
private findFilenameInRow(row: Element): string {
return (
row.querySelector('[class*="SourceListItem__name"],.touch-div,.document_table_list_name_text,.table-file-name,.table-file-name-tooltip-host,.table-list-file-name,.file-name,.filename,.name,[title]')?.textContent?.trim() ||
row.querySelector('[class*="SourceListItem__name"],.f_name_title,.touch-div,.document_table_list_name_text,.table-file-name,.table-file-name-tooltip-host,.table-list-file-name,.file-name,.filename,.name,[title]')?.textContent?.trim() ||
row.getAttribute('title') ||
row.getAttribute('aria-label') ||
''

查看文件

@ -0,0 +1,156 @@
import type { FileItem, PlatformConfig, PlatformName, RenameResult } from '../../types/platform';
import { parseFileName } from '../../utils/helpers';
import { SharedCloudDriveAdapter, type PageScriptAPI } from '../shared/shared-cloud-drive-adapter';
import { getWoozoooPageScriptInjector } from './page-script-injector';
interface WoozoooFileItem {
id?: string | number;
name?: string;
name_all?: string;
size?: string | number;
time?: string;
}
interface WoozoooListResponse {
zt?: string | number;
info?: string | number | unknown[];
text?: WoozoooFileItem[];
dat?: unknown;
}
interface WoozoooRenameResponse {
zt?: string | number;
info?: string;
text?: unknown;
dat?: unknown;
}
export class WoozoooAdapter extends SharedCloudDriveAdapter {
readonly platform: PlatformName = 'woozooo';
constructor(config?: Partial<PlatformConfig>) {
super({ platform: 'woozooo', requestInterval: 800, maxRetries: 3, timeout: 30000, ...config });
}
async getAllFiles(parentId?: string): Promise<FileItem[]> {
const folderId = parentId ?? this.getCurrentFolderId();
const uid = this.getUid();
const vei = this.getVei();
const files: FileItem[] = [];
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await this.callAPI('POST', `https://pc.woozooo.com/doupload.php?uid=${encodeURIComponent(uid)}`, {
bodyMode: 'urlencoded',
entries: [
['task', '5'],
['folder_id', folderId],
['pg', String(page)],
['vei', vei],
],
}) as WoozoooListResponse;
this.assertSuccessful(this.isSuccessful(response), response.zt, this.getErrorMessage(response, '蓝奏云列表获取失败'), response);
if (!Array.isArray(response.text)) {
throw new Error('蓝奏云列表获取失败: missing file list');
}
files.push(...response.text.map((item) => this.toFileItem(item, folderId)));
hasMore = String(response.info) !== '0' && response.text.length > 0;
page += 1;
}
return files;
}
async renameFile(fileId: string, newName: string): Promise<RenameResult> {
return this.retryableRename(async () => {
const response = await this.callAPI('POST', 'https://pc.woozooo.com/doupload.php', {
bodyMode: 'urlencoded',
entries: [
['task', '46'],
['file_id', fileId],
['file_name', parseFileName(newName).name],
['type', '2'],
],
}) as WoozoooRenameResponse;
this.assertSuccessful(this.isSuccessful(response), response.zt, this.getErrorMessage(response, '蓝奏云重命名失败'), response);
}, fileId, newName);
}
protected getCurrentFolderId(): string {
const doc = this.getFileFrameDocument();
return doc?.querySelector<HTMLInputElement>('#folder_id_bibao')?.value || '-1';
}
protected getPageScriptAPI(): PageScriptAPI {
return getWoozoooPageScriptInjector();
}
private toFileItem(item: WoozoooFileItem, parentId: string): FileItem {
const name = item.name_all || item.name || '';
const { ext } = parseFileName(name);
return {
id: String(item.id || ''),
name,
ext,
parentId,
size: Number(item.size ?? 0) || 0,
mtime: this.normalizeTime(item.time),
};
}
private getFileFrameDocument(): Document | null {
const frame = document.querySelector<HTMLIFrameElement>('#mainframe');
try {
return frame?.contentDocument || document;
} catch {
return document;
}
}
private getUid(): string {
const frame = document.querySelector<HTMLIFrameElement>('#mainframe');
const doc = this.getFileFrameDocument();
const candidates = [frame?.src, doc?.location?.href, window.location.href];
for (const href of candidates) {
if (!href) continue;
try {
const uid = new URL(href).searchParams.get('u');
if (uid) return uid;
} catch {
// Ignore malformed or about:blank URLs and try the next source.
}
}
return '';
}
private getVei(): string {
const win = this.getFileFrameWindow();
const sources = [win && typeof win.more === 'function' ? String(win.more) : '', win && typeof win.folder === 'function' ? String(win.folder) : ''];
for (const source of sources) {
const match = source.match(/['"]vei['"]\s*:\s*['"]([^'"]+)['"]/);
if (match?.[1]) return match[1];
}
return '';
}
private getFileFrameWindow(): (Window & typeof globalThis & { more?: unknown; folder?: unknown }) | null {
const frame = document.querySelector<HTMLIFrameElement>('#mainframe');
try {
return (frame?.contentWindow as Window & typeof globalThis & { more?: unknown; folder?: unknown }) || window;
} catch {
return window;
}
}
private isSuccessful(response: { zt?: string | number }): boolean {
return String(response.zt) === '1';
}
private getErrorMessage(response: { info?: unknown }, fallback: string): string {
return typeof response.info === 'string' && response.info ? response.info : fallback;
}
}

查看文件

@ -0,0 +1,17 @@
import { CloudDrivePageScriptInjector } from '../shared/page-script-injector';
let instance: CloudDrivePageScriptInjector | null = null;
export function getWoozoooPageScriptInjector(): CloudDrivePageScriptInjector {
if (!instance) {
instance = new CloudDrivePageScriptInjector({
requestType: 'WOOZOOO_API_REQUEST',
responseType: 'WOOZOOO_API_RESPONSE',
readyFlagName: '__WOOZOOO_PAGE_SCRIPT_READY__',
datasetReadyKey: 'woozoooPageScriptReady',
datasetTimestampKey: 'woozoooPageScriptTimestamp',
logPrefix: 'Woozooo',
});
}
return instance;
}

查看文件

@ -0,0 +1,13 @@
import { installCloudDrivePageScript, type PageScriptInstallOptions } from '../shared/page-script';
export const WOOZOOO_PAGE_SCRIPT_OPTIONS: PageScriptInstallOptions = {
requestType: 'WOOZOOO_API_REQUEST',
responseType: 'WOOZOOO_API_RESPONSE',
readyFlagName: '__WOOZOOO_PAGE_SCRIPT_READY__',
datasetReadyKey: 'woozoooPageScriptReady',
datasetTimestampKey: 'woozoooPageScriptTimestamp',
logPrefix: 'WoozoooPageScript',
captureHeaders: [],
};
installCloudDrivePageScript(WOOZOOO_PAGE_SCRIPT_OPTIONS);

查看文件

@ -10,6 +10,7 @@ import { Drive123Adapter } from '../adapters/123/adapter';
import { CMCCAdapter } from '../adapters/cmcc/adapter';
import { EsurfingAdapter } from '../adapters/esurfing/adapter';
import { XunleiAdapter } from '../adapters/xunlei/adapter';
import { WoozoooAdapter } from '../adapters/woozooo/adapter';
import { storage } from '../utils/storage';
import { STORAGE_KEYS } from '../types/stats';
import { I18nService } from '../utils/i18n';
@ -193,7 +194,7 @@ function cleanupOldInstances(): void {
* 背景: @crxjs/vite-plugin 不支持在 manifest.json 中直接配置 world: "MAIN" 的 TypeScript 文件
* 解决方案: 在 content script (ISOLATED world) 中动态创建 <script> 标签注入到页面
*
* @param platform - 平台名称 (aliyun/baidu/quark/uc/115/123/cmcc/esurfing)
* @param platform - 平台名称 (aliyun/baidu/quark/uc/115/123/cmcc/esurfing/xunlei/woozooo)
*/
async function injectPageScriptToMainWorld(platform: PlatformName): Promise<void> {
// ✅ 引用编译后的 .js 文件(Vite 会将 TypeScript 编译为 JavaScript)
@ -273,6 +274,8 @@ function createPlatformAdapter(platform: PlatformName): PlatformAdapter {
return new EsurfingAdapter();
case 'xunlei':
return new XunleiAdapter();
case 'woozooo':
return new WoozoooAdapter();
}
}

查看文件

@ -39,6 +39,7 @@ const PLATFORM_LABELS: Record<PlatformName, string> = {
cmcc: '移动云盘',
esurfing: '天翼云盘',
xunlei: '迅雷云盘',
woozooo: '蓝奏云',
};
function buildSummary(input: BuildLastFailureDiagnosticSnapshotInput): DiagnosticSummary {

查看文件

@ -1,6 +1,6 @@
{
"extName": "CloudDrive Renamer",
"extDescription": "Powerful batch renaming tool for 115 Drive, 123Pan, Aliyun Drive, Baidu Cloud Drive, CMCC Drive, Esurfing Cloud, Xunlei Drive, Quark Drive, and UC Drive",
"extDescription": "Powerful batch renaming tool for 115 Drive, 123Pan, Aliyun Drive, Baidu Cloud Drive, Lanzou Cloud, CMCC Drive, Esurfing Cloud, Xunlei Drive, Quark Drive, and UC Drive",
"rename_dialog_title": "Batch Rename",
"file_selector_title": "Batch Rename",
"config_panel_title": "Configuration",
@ -188,6 +188,7 @@
"platform_cmcc": "CMCC Drive",
"platform_esurfing": "Esurfing Cloud",
"platform_xunlei": "Xunlei Drive",
"platform_woozooo": "Lanzou Cloud",
"popup_language_label": "Language",
"toast_language_changed": "Language changed to $1",
"toast_language_change_failed": "Failed to change language, please try again",

查看文件

@ -1,6 +1,6 @@
{
"extName": "云盘批量重命名工具",
"extDescription": "强大的批量重命名工具,支持115网盘、123云盘、阿里云盘、百度网盘、移动云盘、天翼云盘、迅雷云盘、夸克网盘、UC网盘文件批量重命名",
"extDescription": "强大的批量重命名工具,支持115网盘、123云盘、阿里云盘、百度网盘、蓝奏云、移动云盘、天翼云盘、迅雷云盘、夸克网盘、UC网盘文件批量重命名",
"rename_dialog_title": "批量重命名",
"file_selector_title": "批量重命名",
"config_panel_title": "配置",
@ -188,6 +188,7 @@
"platform_cmcc": "移动云盘",
"platform_esurfing": "天翼云盘",
"platform_xunlei": "迅雷云盘",
"platform_woozooo": "蓝奏云",
"popup_language_label": "语言 / Language",
"toast_language_changed": "语言已更改为$1",
"toast_language_change_failed": "语言切换失败,请重试",

查看文件

@ -1,6 +1,6 @@
{
"extName": "雲端硬碟批次重新命名工具",
"extDescription": "強大的批次重新命名工具,支援115網盤、123雲盤、阿里雲盤、百度雲端硬碟、移動雲盤、天翼雲盤、迅雷雲盤、夸克網盤、UC網盤檔案批次重新命名",
"extDescription": "強大的批次重新命名工具,支援115網盤、123雲盤、阿里雲盤、百度雲端硬碟、藍奏雲、移動雲盤、天翼雲盤、迅雷雲盤、夸克網盤、UC網盤檔案批次重新命名",
"rename_dialog_title": "批次重新命名",
"file_selector_title": "批次重新命名",
"config_panel_title": "設定",
@ -188,6 +188,7 @@
"platform_cmcc": "移動雲盤",
"platform_esurfing": "天翼雲盤",
"platform_xunlei": "迅雷雲盤",
"platform_woozooo": "藍奏雲",
"popup_language_label": "語言 / Language",
"toast_language_changed": "語言已更改為$1",
"toast_language_change_failed": "語言切換失敗,請重試",

查看文件

@ -1,7 +1,7 @@
/**
* 云盘平台类型
*/
export type PlatformName = 'quark' | 'aliyun' | 'baidu' | 'uc' | '115' | '123' | 'cmcc' | 'esurfing' | 'xunlei';
export type PlatformName = 'quark' | 'aliyun' | 'baidu' | 'uc' | '115' | '123' | 'cmcc' | 'esurfing' | 'xunlei' | 'woozooo';
/**
* 文件项接口

查看文件

@ -123,6 +123,12 @@ export function detectPlatformFromUrl(url: string, pathname?: string): PlatformN
return 'xunlei';
}
// 蓝奏云
if (url.includes('pc.woozooo.com')) {
const pathToCheck = pathname ?? extractPathnameFromUrl(url);
return pathToCheck === '/mydisk.php' ? 'woozooo' : null;
}
// UC网盘
if (url.includes('drive.uc.cn') || url.includes('pan.uc.cn')) {
return 'uc';

查看文件

@ -4,6 +4,7 @@ import { Drive123Adapter } from '../../../src/adapters/123/adapter';
import { CMCCAdapter, md5 } from '../../../src/adapters/cmcc/adapter';
import { EsurfingAdapter } from '../../../src/adapters/esurfing/adapter';
import { XunleiAdapter } from '../../../src/adapters/xunlei/adapter';
import { WoozoooAdapter } from '../../../src/adapters/woozooo/adapter';
import { getDrive115PageScriptInjector } from '../../../src/adapters/115/page-script-injector';
import { getDrive123PageScriptInjector } from '../../../src/adapters/123/page-script-injector';
import { getCMCCPageScriptInjector } from '../../../src/adapters/cmcc/page-script-injector';
@ -11,6 +12,8 @@ import { CMCC_PAGE_SCRIPT_OPTIONS } from '../../../src/adapters/cmcc/page-script
import { getEsurfingPageScriptInjector } from '../../../src/adapters/esurfing/page-script-injector';
import { getXunleiPageScriptInjector } from '../../../src/adapters/xunlei/page-script-injector';
import { XUNLEI_PAGE_SCRIPT_OPTIONS } from '../../../src/adapters/xunlei/page-script';
import { getWoozoooPageScriptInjector } from '../../../src/adapters/woozooo/page-script-injector';
import { WOOZOOO_PAGE_SCRIPT_OPTIONS } from '../../../src/adapters/woozooo/page-script';
vi.mock('../../../src/adapters/115/page-script-injector', () => ({
getDrive115PageScriptInjector: vi.fn(),
@ -32,12 +35,17 @@ vi.mock('../../../src/adapters/xunlei/page-script-injector', () => ({
getXunleiPageScriptInjector: vi.fn(),
}));
vi.mock('../../../src/adapters/woozooo/page-script-injector', () => ({
getWoozoooPageScriptInjector: vi.fn(),
}));
describe('missing platform adapters', () => {
let mock115CallAPI: Mock;
let mock123CallAPI: Mock;
let mockCMCCCallAPI: Mock;
let mockEsurfingCallAPI: Mock;
let mockXunleiCallAPI: Mock;
let mockWoozoooCallAPI: Mock;
beforeEach(() => {
mock115CallAPI = vi.fn();
@ -45,12 +53,14 @@ describe('missing platform adapters', () => {
mockCMCCCallAPI = vi.fn();
mockEsurfingCallAPI = vi.fn();
mockXunleiCallAPI = vi.fn();
mockWoozoooCallAPI = vi.fn();
vi.mocked(getDrive115PageScriptInjector).mockReturnValue({ callAPI: mock115CallAPI });
vi.mocked(getDrive123PageScriptInjector).mockReturnValue({ callAPI: mock123CallAPI });
vi.mocked(getCMCCPageScriptInjector).mockReturnValue({ callAPI: mockCMCCCallAPI });
vi.mocked(getEsurfingPageScriptInjector).mockReturnValue({ callAPI: mockEsurfingCallAPI });
vi.mocked(getXunleiPageScriptInjector).mockReturnValue({ callAPI: mockXunleiCallAPI });
vi.mocked(getWoozoooPageScriptInjector).mockReturnValue({ callAPI: mockWoozoooCallAPI });
Object.defineProperty(window, 'location', {
value: {
@ -214,6 +224,73 @@ describe('missing platform adapters', () => {
]));
});
it('uses Woozooo APIs from the same-origin file iframe and preserves server-managed extensions', async () => {
const adapter = new WoozoooAdapter({ requestInterval: 0 });
const iframe = document.createElement('iframe');
iframe.id = 'mainframe';
iframe.src = 'https://pc.woozooo.com/mydisk.php?item=files&action=index&u=484561';
document.body.appendChild(iframe);
const frameDoc = iframe.contentDocument!;
frameDoc.open();
frameDoc.write(`
<input id="folder_id_bibao" value="8006795" />
<script>function more(folder_id){ $.ajax({ data: { 'task':5,'folder_id':folder_id,'pg':pgs,'vei':'B1VQUgNRAw9TBFdW' } }); }</script>
`);
frameDoc.close();
mockWoozoooCallAPI
.mockResolvedValueOnce({
zt: 1,
info: 1,
text: [{ id: '308207718', name: '未命名项目-图层 1.png', name_all: '未命名项目-图层 1.png', size: '246.6 K', time: '3 分钟前' }],
})
.mockResolvedValueOnce({
zt: 1,
info: 1,
text: [{ id: '308207716', name: '数字键盘.jpg', name_all: '数字键盘.jpg', size: '68.2 K', time: '3 分钟前' }],
})
.mockResolvedValueOnce({ zt: 1, info: 0, text: [] })
.mockResolvedValueOnce({ zt: 1, info: '新名称' });
const files = await adapter.getAllFiles();
const rename = await adapter.renameFile('308207718', '新名称.png');
expect(files).toHaveLength(2);
expect(files[0]).toMatchObject({ id: '308207718', name: '未命名项目-图层 1.png', ext: '.png', parentId: '8006795', size: 0 });
expect(files[1]).toMatchObject({ id: '308207716', name: '数字键盘.jpg', ext: '.jpg', parentId: '8006795', size: 0 });
expect(rename).toEqual({ success: true, newName: '新名称.png' });
expect(mockWoozoooCallAPI).toHaveBeenNthCalledWith(
1,
'POST',
'https://pc.woozooo.com/doupload.php?uid=484561',
{
bodyMode: 'urlencoded',
entries: [
['task', '5'],
['folder_id', '8006795'],
['pg', '1'],
['vei', 'B1VQUgNRAw9TBFdW'],
],
},
30000
);
expect(mockWoozoooCallAPI).toHaveBeenNthCalledWith(
4,
'POST',
'https://pc.woozooo.com/doupload.php',
{
bodyMode: 'urlencoded',
entries: [
['task', '46'],
['file_id', '308207718'],
['file_name', '新名称'],
['type', '2'],
],
},
30000
);
});
it('uses Xunlei APIs for listing and rename', async () => {
const adapter = new XunleiAdapter({ requestInterval: 0 });
Object.defineProperty(window, 'location', {
@ -264,6 +341,10 @@ describe('missing platform adapters', () => {
);
});
it('uses no special captured headers for Woozooo page-script requests', () => {
expect(WOOZOOO_PAGE_SCRIPT_OPTIONS.captureHeaders ?? []).toEqual([]);
});
it('throws instead of returning an empty list when Xunlei list API fails', async () => {
const adapter = new XunleiAdapter({ requestInterval: 0 });
mockXunleiCallAPI.mockResolvedValue({ error: 'bad_request', error_description: 'list failed' });
@ -567,6 +648,25 @@ describe('missing platform adapters', () => {
expect(querySelectorAll).not.toHaveBeenCalled();
});
it('patches Woozooo iframe rows with f_name_title nodes after rename', async () => {
const adapter = new WoozoooAdapter({ requestInterval: 0 });
const iframe = document.createElement('iframe');
iframe.id = 'mainframe';
document.body.appendChild(iframe);
const frameDoc = iframe.contentDocument!;
frameDoc.body.innerHTML = `
<div id="f308207718" class="f_tb">
<div class="f_name"><span class="f_name_title" id="filename308207718">旧名称.png</span></div>
</div>
`;
const result = await adapter.syncAfterRename([{ fileId: '308207718', oldName: '旧名称.png', newName: '新名称.png' }]);
expect(result.success).toBe(true);
expect(result.method).toBe('dom-patch');
expect(frameDoc.querySelector('#filename308207718')?.textContent).toBe('新名称.png');
});
it('patches visible rows for new adapters instead of reporting unsupported sync', async () => {
document.body.innerHTML = `
<table>

查看文件

@ -45,6 +45,16 @@ describe('manifest platform matches', () => {
expect(webAccessibleMatches).toContain('https://pan.xunlei.com/*');
});
it('injects Woozooo UI on pc.woozooo.com and allows its same-origin API', () => {
const manifest = loadManifest();
const contentScriptMatches = manifest.content_scripts?.flatMap((script) => script.matches ?? []) ?? [];
const webAccessibleMatches = manifest.web_accessible_resources?.flatMap((resource) => resource.matches ?? []) ?? [];
expect(manifest.host_permissions).toContain('https://pc.woozooo.com/*');
expect(contentScriptMatches).toContain('https://pc.woozooo.com/*');
expect(webAccessibleMatches).toContain('https://pc.woozooo.com/*');
});
it('injects CMCC UI on yun.139.com and allows its API host', () => {
const manifest = loadManifest();
const contentScriptMatches = manifest.content_scripts?.flatMap((script) => script.matches ?? []) ?? [];

查看文件

@ -242,6 +242,22 @@ describe('Platform Detection - Unified Utils', () => {
expect(result).toBe('cmcc');
});
it('should detect Woozooo Drive on the top-level file manager page', () => {
const result = detectPlatformFromUrl(
'https://pc.woozooo.com/mydisk.php',
'/mydisk.php'
);
expect(result).toBe('woozooo');
});
it('should detect Woozooo Drive on the same-origin file iframe page', () => {
const result = detectPlatformFromUrl(
'https://pc.woozooo.com/mydisk.php?item=files&action=index&u=484561',
'/mydisk.php'
);
expect(result).toBe('woozooo');
});
it('should detect Xunlei Drive', () => {
const result = detectPlatformFromUrl(
'https://pan.xunlei.com/',

查看文件

@ -18,6 +18,7 @@ export default defineConfig({
'page-script-cmcc': 'src/adapters/cmcc/page-script.ts',
'page-script-esurfing': 'src/adapters/esurfing/page-script.ts',
'page-script-xunlei': 'src/adapters/xunlei/page-script.ts',
'page-script-woozooo': 'src/adapters/woozooo/page-script.ts',
},
output: {
entryFileNames: (chunkInfo) => {