镜像自地址
https://github.com/lhly/cloud-drive-renamer.git
已同步 2026-10-20 13:47:47 +08:00
refactor(platform-detection): 统一平台检测逻辑并添加分享链接识别
将 content script 和 popup 中的平台检测逻辑统一迁移到 platform-detector 工具模块 添加夸克网盘分享链接检测功能,并在检测到分享链接时禁用扩展 更新相关测试用例确保检测逻辑正确性
这个提交包含在:
父节点
9e45a7e51e
当前提交
f9f93cca59
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cloud-drive-renamer",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"description": "批量重命名工具,支持夸克网盘(阿里云盘和百度网盘开发中)",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@ -9,6 +9,7 @@ import { storage } from '../utils/storage';
|
||||
import { PlatformUsageStats, STORAGE_KEYS } from '../types/stats';
|
||||
import { I18nService } from '../utils/i18n';
|
||||
import type { LanguageChangeMessage } from '../types/i18n';
|
||||
import { detectPlatformFromUrl, isQuarkShareLink } from '../utils/platform-detector';
|
||||
|
||||
/**
|
||||
* Content Script
|
||||
@ -33,18 +34,20 @@ let initRetryCount = 0;
|
||||
const MAX_INIT_RETRIES = 3;
|
||||
|
||||
// 检测当前平台
|
||||
function detectPlatform(): PlatformName | null {
|
||||
// 使用统一的平台检测工具函数
|
||||
export function detectPlatform(): PlatformName | null {
|
||||
const url = window.location.href;
|
||||
const pathname = window.location.pathname;
|
||||
|
||||
if (url.includes('pan.quark.cn')) {
|
||||
return 'quark';
|
||||
} else if (url.includes('www.aliyundrive.com')) {
|
||||
return 'aliyun';
|
||||
} else if (url.includes('pan.baidu.com')) {
|
||||
return 'baidu';
|
||||
// 使用统一的检测逻辑
|
||||
const platform = detectPlatformFromUrl(url, pathname);
|
||||
|
||||
// 如果是分享链接,记录日志 (提升日志级别为 warn,因为这是需要用户关注的情况)
|
||||
if (url.includes('pan.quark.cn') && isQuarkShareLink(pathname)) {
|
||||
logger.warn('Quark share link detected, extension disabled for share pages');
|
||||
}
|
||||
|
||||
return null;
|
||||
return platform;
|
||||
}
|
||||
|
||||
// 初始化
|
||||
|
||||
@ -5,19 +5,14 @@ import { PlatformUsageStats, STORAGE_KEYS } from '../types/stats';
|
||||
import { APP_VERSION_WITH_PREFIX } from '../shared/version';
|
||||
import { I18nService } from '../utils/i18n';
|
||||
import { LanguageChangeMessage } from '../types/i18n';
|
||||
import { detectPlatformFromUrl } from '../utils/platform-detector';
|
||||
|
||||
/**
|
||||
* Popup脚本
|
||||
*/
|
||||
|
||||
// 平台URL到平台标识的映射
|
||||
// 注意:只包含真正实现了content script和adapter的平台
|
||||
const PLATFORM_URL_MAP: Record<string, PlatformName> = {
|
||||
'pan.quark.cn': 'quark',
|
||||
// 未来支持的平台在此添加
|
||||
// 'www.aliyundrive.com': 'aliyun',
|
||||
// 'pan.baidu.com': 'baidu',
|
||||
};
|
||||
// 注意: 平台检测逻辑已迁移到 src/utils/platform-detector.ts
|
||||
// 使用统一的 detectPlatformFromUrl() 函数
|
||||
|
||||
/**
|
||||
* 获取平台显示名称(动态翻译)
|
||||
@ -126,6 +121,7 @@ async function initPopup() {
|
||||
}
|
||||
|
||||
// 检测平台
|
||||
// 使用统一的检测逻辑 (移除旧的本地实现)
|
||||
const platformKey = detectPlatformFromUrl(currentTab.url);
|
||||
if (platformKey) {
|
||||
// 使用统一的函数获取翻译
|
||||
@ -151,15 +147,8 @@ async function initPopup() {
|
||||
bindEvents();
|
||||
}
|
||||
|
||||
// 检测平台
|
||||
function detectPlatformFromUrl(url: string): PlatformName | null {
|
||||
for (const [urlPattern, platformKey] of Object.entries(PLATFORM_URL_MAP)) {
|
||||
if (url.includes(urlPattern)) {
|
||||
return platformKey;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// 注意: detectPlatformFromUrl() 已迁移到 src/utils/platform-detector.ts
|
||||
// 这里不再需要本地实现
|
||||
|
||||
// 更新平台名称显示
|
||||
function updatePlatformName(name: string) {
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
* 1. Edit version in package.json
|
||||
* 2. Run: npm run sync:version (or automatically via prebuild/predev)
|
||||
*
|
||||
* Generated at: 2025-12-18T13:53:59.891Z
|
||||
* Generated at: 2025-12-18T14:55:23.819Z
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
75
src/utils/platform-detector.ts
普通文件
75
src/utils/platform-detector.ts
普通文件
@ -0,0 +1,75 @@
|
||||
/**
|
||||
* 平台检测工具模块
|
||||
* 统一 content script 和 popup 的平台检测逻辑
|
||||
*/
|
||||
|
||||
import { PlatformName } from '../types/platform';
|
||||
|
||||
/**
|
||||
* 夸克网盘分享链接路径模式
|
||||
* 所有包含这些模式的路径都被识别为分享链接页面
|
||||
*/
|
||||
export const QUARK_SHARE_LINK_PATTERNS = ['/s/'] as const;
|
||||
|
||||
/**
|
||||
* 检测URL是否为夸克网盘分享链接
|
||||
* @param pathname - URL路径部分
|
||||
* @returns 是否为分享链接
|
||||
*/
|
||||
export function isQuarkShareLink(pathname: string | null | undefined): boolean {
|
||||
// 防御性检查: 确保 pathname 存在且为字符串
|
||||
if (!pathname || typeof pathname !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查路径是否包含任何分享链接模式
|
||||
return QUARK_SHARE_LINK_PATTERNS.some(pattern => pathname.includes(pattern));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从URL检测平台类型
|
||||
* @param url - 完整URL
|
||||
* @param pathname - URL路径部分 (可选, 用于分享链接检测)
|
||||
* @returns 平台名称或null
|
||||
*/
|
||||
export function detectPlatformFromUrl(url: string, pathname?: string): PlatformName | null {
|
||||
// 夸克网盘
|
||||
if (url.includes('pan.quark.cn')) {
|
||||
// 使用统一的分享链接检测函数
|
||||
// 如果未提供pathname,尝试从URL解析
|
||||
const pathToCheck = pathname ?? extractPathnameFromUrl(url);
|
||||
|
||||
if (isQuarkShareLink(pathToCheck)) {
|
||||
return null; // 分享链接页面不支持
|
||||
}
|
||||
|
||||
return 'quark';
|
||||
}
|
||||
|
||||
// 阿里云盘 (未来支持)
|
||||
if (url.includes('www.aliyundrive.com')) {
|
||||
return 'aliyun';
|
||||
}
|
||||
|
||||
// 百度网盘 (未来支持)
|
||||
if (url.includes('pan.baidu.com')) {
|
||||
return 'baidu';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从完整URL中提取pathname部分
|
||||
* @param url - 完整URL
|
||||
* @returns pathname字符串
|
||||
*/
|
||||
function extractPathnameFromUrl(url: string): string {
|
||||
try {
|
||||
const urlObj = new URL(url);
|
||||
return urlObj.pathname;
|
||||
} catch {
|
||||
// URL解析失败,返回空字符串
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,255 @@
|
||||
/**
|
||||
* 平台检测单元测试
|
||||
* 验证统一的平台检测工具函数正确识别个人网盘和分享链接
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { detectPlatformFromUrl, isQuarkShareLink, QUARK_SHARE_LINK_PATTERNS } from '../../src/utils/platform-detector';
|
||||
import { detectPlatform } from '../../src/content/index';
|
||||
import { logger } from '../../src/utils/logger';
|
||||
|
||||
// Mock logger 模块
|
||||
vi.mock('../../src/utils/logger', () => ({
|
||||
logger: {
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
}
|
||||
}));
|
||||
|
||||
describe('Platform Detection - Unified Utils', () => {
|
||||
beforeEach(() => {
|
||||
// 重置所有 mock
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('isQuarkShareLink() - Share Link Pattern Detection', () => {
|
||||
it('should detect basic share link pattern', () => {
|
||||
expect(isQuarkShareLink('/s/abc123xyz')).toBe(true);
|
||||
});
|
||||
|
||||
it('should detect share link with trailing slash', () => {
|
||||
expect(isQuarkShareLink('/s/abc123xyz/')).toBe(true);
|
||||
});
|
||||
|
||||
it('should detect share link with nested path', () => {
|
||||
expect(isQuarkShareLink('/s/share/folder')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for personal storage path', () => {
|
||||
expect(isQuarkShareLink('/list')).toBe(false);
|
||||
expect(isQuarkShareLink('/list/all')).toBe(false);
|
||||
expect(isQuarkShareLink('/settings')).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle null pathname defensively', () => {
|
||||
expect(isQuarkShareLink(null)).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle undefined pathname defensively', () => {
|
||||
expect(isQuarkShareLink(undefined)).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle empty string', () => {
|
||||
expect(isQuarkShareLink('')).toBe(false);
|
||||
});
|
||||
|
||||
it('should use configurable patterns from constant', () => {
|
||||
// 验证函数使用了导出的常量
|
||||
expect(QUARK_SHARE_LINK_PATTERNS).toContain('/s/');
|
||||
});
|
||||
});
|
||||
|
||||
describe('detectPlatformFromUrl() - Unified Detection Logic', () => {
|
||||
describe('Quark Drive - Personal Storage', () => {
|
||||
it('should detect personal file list page', () => {
|
||||
const result = detectPlatformFromUrl(
|
||||
'https://pan.quark.cn/list#/list/all',
|
||||
'/list'
|
||||
);
|
||||
expect(result).toBe('quark');
|
||||
});
|
||||
|
||||
it('should detect personal folder page', () => {
|
||||
const result = detectPlatformFromUrl(
|
||||
'https://pan.quark.cn/list#/list/folder/12345',
|
||||
'/list'
|
||||
);
|
||||
expect(result).toBe('quark');
|
||||
});
|
||||
|
||||
it('should detect personal recent files page', () => {
|
||||
const result = detectPlatformFromUrl(
|
||||
'https://pan.quark.cn/list#/recent',
|
||||
'/list'
|
||||
);
|
||||
expect(result).toBe('quark');
|
||||
});
|
||||
|
||||
it('should detect settings page', () => {
|
||||
const result = detectPlatformFromUrl(
|
||||
'https://pan.quark.cn/settings',
|
||||
'/settings'
|
||||
);
|
||||
expect(result).toBe('quark');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Quark Drive - Share Links (Should Return null)', () => {
|
||||
it('should return null for basic share link', () => {
|
||||
const result = detectPlatformFromUrl(
|
||||
'https://pan.quark.cn/s/abc123xyz',
|
||||
'/s/abc123xyz'
|
||||
);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for share link with password', () => {
|
||||
const result = detectPlatformFromUrl(
|
||||
'https://pan.quark.cn/s/abc123xyz?pwd=1234',
|
||||
'/s/abc123xyz'
|
||||
);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for share link with hash fragment', () => {
|
||||
const result = detectPlatformFromUrl(
|
||||
'https://pan.quark.cn/s/xyz789#/folder',
|
||||
'/s/xyz789'
|
||||
);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for share link with query parameters', () => {
|
||||
const result = detectPlatformFromUrl(
|
||||
'https://pan.quark.cn/s/shared123?from=mobile&source=qr',
|
||||
'/s/shared123'
|
||||
);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should handle pathname with trailing slash', () => {
|
||||
const result = detectPlatformFromUrl(
|
||||
'https://pan.quark.cn/s/test123/',
|
||||
'/s/test123/'
|
||||
);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should handle nested share path', () => {
|
||||
const result = detectPlatformFromUrl(
|
||||
'https://pan.quark.cn/s/share/folder',
|
||||
'/s/share/folder'
|
||||
);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Auto pathname extraction (when pathname not provided)', () => {
|
||||
it('should extract pathname from URL for personal storage', () => {
|
||||
const result = detectPlatformFromUrl('https://pan.quark.cn/list#/list/all');
|
||||
expect(result).toBe('quark');
|
||||
});
|
||||
|
||||
it('should extract pathname from URL for share link', () => {
|
||||
const result = detectPlatformFromUrl('https://pan.quark.cn/s/abc123xyz');
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Other Platforms - Unchanged Behavior', () => {
|
||||
it('should detect Aliyun Drive', () => {
|
||||
const result = detectPlatformFromUrl(
|
||||
'https://www.aliyundrive.com/drive/file/all',
|
||||
'/drive/file/all'
|
||||
);
|
||||
expect(result).toBe('aliyun');
|
||||
});
|
||||
|
||||
it('should detect Baidu Drive', () => {
|
||||
const result = detectPlatformFromUrl(
|
||||
'https://pan.baidu.com/disk/home',
|
||||
'/disk/home'
|
||||
);
|
||||
expect(result).toBe('baidu');
|
||||
});
|
||||
|
||||
it('should return null for unsupported platform', () => {
|
||||
const result = detectPlatformFromUrl(
|
||||
'https://example.com/',
|
||||
'/'
|
||||
);
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge Cases - Robustness', () => {
|
||||
it('should handle malformed URL gracefully', () => {
|
||||
const result = detectPlatformFromUrl('not-a-valid-url');
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should handle empty pathname', () => {
|
||||
const result = detectPlatformFromUrl('https://pan.quark.cn/', '');
|
||||
expect(result).toBe('quark');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('detectPlatform() - Content Script Integration', () => {
|
||||
beforeEach(() => {
|
||||
// 重置 window.location mock
|
||||
delete (window as any).location;
|
||||
});
|
||||
|
||||
it('should detect personal storage and not log warning', () => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: {
|
||||
href: 'https://pan.quark.cn/list#/list/all',
|
||||
pathname: '/list'
|
||||
},
|
||||
writable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
const result = detectPlatform();
|
||||
|
||||
expect(result).toBe('quark');
|
||||
expect(logger.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should detect share link and log warning', () => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: {
|
||||
href: 'https://pan.quark.cn/s/abc123xyz',
|
||||
pathname: '/s/abc123xyz'
|
||||
},
|
||||
writable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
const result = detectPlatform();
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(logger.warn).toHaveBeenCalledWith(
|
||||
'Quark share link detected, extension disabled for share pages'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return null for unsupported platform without warning', () => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: {
|
||||
href: 'https://example.com/',
|
||||
pathname: '/'
|
||||
},
|
||||
writable: true,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
const result = detectPlatform();
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(logger.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
正在加载...
在新工单中引用
屏蔽一个用户