From d32e5ef66cfc47559736a1dfe40e07614043e933 Mon Sep 17 00:00:00 2001 From: Zain Date: Sat, 15 Aug 2026 10:37:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(woozooo):=20=E6=96=B0=E5=A2=9E=E8=93=9D?= =?UTF-8?q?=E5=A5=8F=E4=BA=91=E7=BD=91=E7=9B=98=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增蓝奏云网盘适配,支持文件列表获取、重命名及域名注入; 更新相关文档、测试用例及多语言描述,将蓝奏云纳入支持平台。 --- README.md | 14 +- _locales/en/messages.json | 2 +- _locales/zh_CN/messages.json | 2 +- _locales/zh_TW/messages.json | 2 +- docs/store-assets/PRIVACY_POLICY.md | 3 + docs/store-assets/SUBMISSION_GUIDE.md | 4 +- docs/store-assets/chrome-web-store-listing.md | 11 +- docs/store-assets/edge-addons-listing.md | 15 +- docs/store-assets/edge-supplemental-info.md | 4 +- manifest.json | 18 +- package.json | 4 +- .../shared/shared-cloud-drive-adapter.ts | 15 +- src/adapters/woozooo/adapter.ts | 156 ++++++++++++++++++ src/adapters/woozooo/page-script-injector.ts | 17 ++ src/adapters/woozooo/page-script.ts | 13 ++ src/content/index.ts | 5 +- src/core/diagnostic-session.ts | 1 + src/locales/en.json | 3 +- src/locales/zh_CN.json | 3 +- src/locales/zh_TW.json | 3 +- src/types/platform.ts | 2 +- src/utils/platform-detector.ts | 6 + .../missing-platform-adapters.test.ts | 100 +++++++++++ tests/unit/manifest-platform-matches.test.ts | 10 ++ tests/unit/platform-detection.test.ts | 16 ++ vite.config.ts | 1 + 26 files changed, 397 insertions(+), 33 deletions(-) create mode 100644 src/adapters/woozooo/adapter.ts create mode 100644 src/adapters/woozooo/page-script-injector.ts create mode 100644 src/adapters/woozooo/page-script.ts diff --git a/README.md b/README.md index f35f590..17bc675 100644 --- a/README.md +++ b/README.md @@ -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 网盘 - 支持文件批量重命名和页面同步 diff --git a/_locales/en/messages.json b/_locales/en/messages.json index fa5e14a..b10110d 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -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" }, diff --git a/_locales/zh_CN/messages.json b/_locales/zh_CN/messages.json index cf75876..aaa6bd9 100644 --- a/_locales/zh_CN/messages.json +++ b/_locales/zh_CN/messages.json @@ -4,7 +4,7 @@ "description": "扩展名称" }, "extDescription": { - "message": "强大的批量重命名工具,支持夸克网盘、阿里云盘、百度网盘文件批量重命名", + "message": "强大的批量重命名工具,支持夸克网盘、阿里云盘、百度网盘、115网盘、123云盘、移动云盘、天翼云盘、迅雷云盘、蓝奏云、UC网盘文件批量重命名", "description": "扩展描述" }, diff --git a/_locales/zh_TW/messages.json b/_locales/zh_TW/messages.json index 368289e..8872ed5 100644 --- a/_locales/zh_TW/messages.json +++ b/_locales/zh_TW/messages.json @@ -4,7 +4,7 @@ "description": "擴充功能名稱" }, "extDescription": { - "message": "強大的批次重新命名工具,支援夸克網盤、阿里雲盤、百度雲端硬碟檔案批次重新命名", + "message": "強大的批次重新命名工具,支援夸克網盤、阿里雲盤、百度雲端硬碟、115網盤、123雲盤、移動雲盤、天翼雲盤、迅雷雲盤、藍奏雲、UC網盤檔案批次重新命名", "description": "擴充功能描述" }, diff --git a/docs/store-assets/PRIVACY_POLICY.md b/docs/store-assets/PRIVACY_POLICY.md index d985e8f..e78e940 100644 --- a/docs/store-assets/PRIVACY_POLICY.md +++ b/docs/store-assets/PRIVACY_POLICY.md @@ -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 diff --git a/docs/store-assets/SUBMISSION_GUIDE.md b/docs/store-assets/SUBMISSION_GUIDE.md index b2ea6bd..3b54e2e 100644 --- a/docs/store-assets/SUBMISSION_GUIDE.md +++ b/docs/store-assets/SUBMISSION_GUIDE.md @@ -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(详细描述)** diff --git a/docs/store-assets/chrome-web-store-listing.md b/docs/store-assets/chrome-web-store-listing.md index 739fad5..e18bbb3 100644 --- a/docs/store-assets/chrome-web-store-listing.md +++ b/docs/store-assets/chrome-web-store-listing.md @@ -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 网盘页面 diff --git a/docs/store-assets/edge-addons-listing.md b/docs/store-assets/edge-addons-listing.md index 752a2f1..c240243 100644 --- a/docs/store-assets/edge-addons-listing.md +++ b/docs/store-assets/edge-addons-listing.md @@ -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 网盘备用页面域名 | diff --git a/docs/store-assets/edge-supplemental-info.md b/docs/store-assets/edge-supplemental-info.md index 2488bb4..3006844 100644 --- a/docs/store-assets/edge-supplemental-info.md +++ b/docs/store-assets/edge-supplemental-info.md @@ -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(详细描述) diff --git a/manifest.json b/manifest.json index 89e0e35..294de76 100644 --- a/manifest.json +++ b/manifest.json @@ -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/*" ] } ], diff --git a/package.json b/package.json index 0bad91e..acfc349 100644 --- a/package.json +++ b/package.json @@ -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" ], diff --git a/src/adapters/shared/shared-cloud-drive-adapter.ts b/src/adapters/shared/shared-cloud-drive-adapter.ts index 5b529ab..940237f 100644 --- a/src/adapters/shared/shared-cloud-drive-adapter.ts +++ b/src/adapters/shared/shared-cloud-drive-adapter.ts @@ -238,7 +238,7 @@ export abstract class SharedCloudDriveAdapter extends BasePlatformAdapter { private applyRenameMappingToDomById(renameInfoById: Map, 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(); 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') || '' diff --git a/src/adapters/woozooo/adapter.ts b/src/adapters/woozooo/adapter.ts new file mode 100644 index 0000000..b28ba7e --- /dev/null +++ b/src/adapters/woozooo/adapter.ts @@ -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) { + super({ platform: 'woozooo', requestInterval: 800, maxRetries: 3, timeout: 30000, ...config }); + } + + async getAllFiles(parentId?: string): Promise { + 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 { + 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('#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('#mainframe'); + try { + return frame?.contentDocument || document; + } catch { + return document; + } + } + + private getUid(): string { + const frame = document.querySelector('#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('#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; + } +} diff --git a/src/adapters/woozooo/page-script-injector.ts b/src/adapters/woozooo/page-script-injector.ts new file mode 100644 index 0000000..aa0c88d --- /dev/null +++ b/src/adapters/woozooo/page-script-injector.ts @@ -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; +} diff --git a/src/adapters/woozooo/page-script.ts b/src/adapters/woozooo/page-script.ts new file mode 100644 index 0000000..425d3a4 --- /dev/null +++ b/src/adapters/woozooo/page-script.ts @@ -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); diff --git a/src/content/index.ts b/src/content/index.ts index e20fd29..62e3a08 100644 --- a/src/content/index.ts +++ b/src/content/index.ts @@ -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) 中动态创建 + `); + 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 = ` +
+
旧名称.png
+
+ `; + + 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 = ` diff --git a/tests/unit/manifest-platform-matches.test.ts b/tests/unit/manifest-platform-matches.test.ts index ec2b4ce..438ef57 100644 --- a/tests/unit/manifest-platform-matches.test.ts +++ b/tests/unit/manifest-platform-matches.test.ts @@ -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 ?? []) ?? []; diff --git a/tests/unit/platform-detection.test.ts b/tests/unit/platform-detection.test.ts index 1b7a73a..2342642 100644 --- a/tests/unit/platform-detection.test.ts +++ b/tests/unit/platform-detection.test.ts @@ -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/', diff --git a/vite.config.ts b/vite.config.ts index 22cd2ed..4030894 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -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) => {