镜像自地址
https://github.com/lhly/search-syntax-pro.git
已同步 2026-10-20 22:37:48 +08:00
- 新增Yandex和DuckDuckGo搜索引擎适配器配置 - 更新manifest.json添加新搜索引擎权限 - 优化URL工具类支持更多搜索引擎参数解析 - 移除调试日志输出 - 修复百度搜索输入框选择器兼容性问题 - 添加文档归档和历史需求规范 版本升级至1.8.6
1579 行
45 KiB
Markdown
1579 行
45 KiB
Markdown
# Technical Specification - Search Engine Adapters Expansion
|
|
|
|
**Project**: SearchSyntax Pro v1.8.1
|
|
**Feature**: 新增 7 个搜索引擎适配器支持
|
|
**Document Version**: 1.0
|
|
**Created**: 2025-11-26
|
|
**Status**: Ready for Implementation
|
|
|
|
---
|
|
|
|
## 1. Problem Statement
|
|
|
|
### Business Issue
|
|
SearchSyntax Pro 当前仅支持 10 个搜索引擎,缺乏对多个主流搜索引擎的支持,限制了用户在特定市场和隐私友好搜索场景下的选择。
|
|
|
|
### Current State
|
|
- 已支持引擎: Baidu, Google, Bing, DuckDuckGo, Brave, Yandex, Twitter, Reddit, GitHub, Stack Overflow
|
|
- 缺失引擎: Yahoo, Startpage, Ecosia, Qwant, Naver, Sogou, 360搜索
|
|
- 用户无法使用这些搜索引擎的高级语法功能
|
|
|
|
### Expected Outcome
|
|
添加 7 个新搜索引擎适配器,所有语法支持与官方搜索引擎能力保持一致,提供完整的中英文国际化支持。
|
|
|
|
---
|
|
|
|
## 2. Solution Overview
|
|
|
|
### Approach
|
|
采用现有适配器模式,通过复用 Google/Bing/Baidu 适配器逻辑,实现 7 个新搜索引擎的适配器。分三个阶段实施,优先兼容性引擎,再处理区域市场引擎。
|
|
|
|
### Core Changes
|
|
1. **适配器实现**: 创建 7 个新的适配器类文件
|
|
2. **类型定义**: 扩展 `SearchEngine` 类型
|
|
3. **工厂注册**: 在 factory.ts 注册新适配器
|
|
4. **国际化**: 添加引擎名称和验证消息翻译
|
|
5. **测试**: 为核心逻辑编写单元测试
|
|
|
|
### Success Criteria
|
|
- 所有 7 个引擎可正常构建查询 URL
|
|
- 语法支持与官方文档一致
|
|
- 多关键词 OR 组合在支持的引擎中正常工作
|
|
- 语法降级正确处理不支持的语法
|
|
- 所有用户可见文本有中英文翻译
|
|
- 核心逻辑单元测试通过
|
|
|
|
---
|
|
|
|
## 3. Technical Implementation
|
|
|
|
### 3.1 Database Changes
|
|
**无数据库变更** - 仅使用 Chrome Storage API
|
|
|
|
### 3.2 Code Changes
|
|
|
|
#### Phase 1: 兼容性引擎 (3个)
|
|
|
|
##### 3.2.1 Yahoo Search Adapter
|
|
|
|
**文件路径**: `/Users/lhly/chromeex/ssp/src/services/adapters/yahoo.ts`
|
|
|
|
**实现规格**:
|
|
```typescript
|
|
/**
|
|
* Yahoo 搜索引擎适配器
|
|
* 基于 Bing 语法兼容性实现
|
|
*/
|
|
export class YahooAdapter implements SearchEngineAdapter {
|
|
getName(): string {
|
|
return '雅虎' // 将通过 i18n 翻译
|
|
}
|
|
|
|
getBaseUrl(): string {
|
|
return 'https://search.yahoo.com/search'
|
|
}
|
|
|
|
// 查询参数格式: ?p={query}
|
|
buildQuery(params: SearchParams): string {
|
|
const baseUrl = this.getBaseUrl()
|
|
const query = this.buildSearchQuery(params)
|
|
return `${baseUrl}?p=${encodeURIComponent(query)}`
|
|
}
|
|
|
|
// 支持的语法 (与 Bing 相同)
|
|
getSupportedSyntax(): SyntaxType[] {
|
|
return [
|
|
'site',
|
|
'filetype',
|
|
'exact',
|
|
'date_range',
|
|
'intitle',
|
|
'inurl',
|
|
'exclude',
|
|
'or',
|
|
'intext', // Yahoo 使用 inbody:
|
|
'number_range',
|
|
'wildcard',
|
|
'allintitle', // 降级为多个 intitle
|
|
'related'
|
|
// 不支持 'cache'
|
|
]
|
|
}
|
|
|
|
// 多关键词支持 (与 Bing 一致)
|
|
// - sites: 支持 OR 组合
|
|
// - fileTypes: 支持 OR 组合
|
|
// - exactMatches: 原生并列支持
|
|
|
|
// 语法降级
|
|
degradeSyntax(params: SearchParams): SearchParams {
|
|
const degradedParams = { ...params }
|
|
if (params.cacheSite) {
|
|
console.warn('Yahoo不支持cache语法,该参数将被忽略')
|
|
degradedParams.cacheSite = undefined
|
|
}
|
|
return degradedParams
|
|
}
|
|
|
|
// UI 功能特性
|
|
getSupportedFeatures(): UIFeatureType[] {
|
|
return [
|
|
'site', 'filetype', 'intitle', 'inurl', 'intext',
|
|
'exact_match', 'exclude', 'or_keywords',
|
|
'date_range', 'related'
|
|
]
|
|
}
|
|
|
|
getFeatureGroups(): EngineFeatureGroups {
|
|
return {
|
|
location: ['site', 'filetype', 'intitle', 'inurl', 'intext'],
|
|
precision: ['exact_match'],
|
|
logic: ['exclude', 'or_keywords'],
|
|
range: ['date_range'],
|
|
special: ['related']
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**关键实现细节**:
|
|
- **baseUrl**: `https://search.yahoo.com/search`
|
|
- **查询参数**: `?p=` (区别于 Google 的 `?q=`)
|
|
- **语法处理**: 完全复用 BingAdapter 的 buildSearchQuery 逻辑
|
|
- **intext 映射**: 使用 `inbody:` (与 Bing 一致)
|
|
- **allintitle 降级**: 转换为多个 `intitle:` (与 Bing 一致)
|
|
- **cache 降级**: 设为 undefined 并记录警告
|
|
|
|
##### 3.2.2 Startpage Adapter
|
|
|
|
**文件路径**: `/Users/lhly/chromeex/ssp/src/services/adapters/startpage.ts`
|
|
|
|
**实现规格**:
|
|
```typescript
|
|
/**
|
|
* Startpage 搜索引擎适配器
|
|
* 基于 Google 语法兼容性实现 (隐私友好)
|
|
*/
|
|
export class StartpageAdapter implements SearchEngineAdapter {
|
|
getName(): string {
|
|
return 'Startpage'
|
|
}
|
|
|
|
getBaseUrl(): string {
|
|
return 'https://www.startpage.com/sp/search'
|
|
}
|
|
|
|
// 查询参数格式: ?query={query}
|
|
buildQuery(params: SearchParams): string {
|
|
const baseUrl = this.getBaseUrl()
|
|
const query = this.buildSearchQuery(params)
|
|
return `${baseUrl}?query=${encodeURIComponent(query)}`
|
|
}
|
|
|
|
// 支持的语法 (Google 子集)
|
|
getSupportedSyntax(): SyntaxType[] {
|
|
return [
|
|
'site',
|
|
'filetype',
|
|
'exact',
|
|
'date_range', // before:/after:
|
|
'intitle',
|
|
'exclude',
|
|
'or'
|
|
// 不支持: inurl, intext, wildcard, allintitle, related, cache, number_range
|
|
]
|
|
}
|
|
|
|
// 多关键词支持 (与 Google 一致)
|
|
// - sites: 支持 OR 组合
|
|
// - fileTypes: 支持 OR 组合
|
|
// - exactMatches: 原生并列支持
|
|
|
|
// 语法降级
|
|
degradeSyntax(params: SearchParams): SearchParams {
|
|
const degradedParams = { ...params }
|
|
const warnings: string[] = []
|
|
|
|
// Startpage 不支持的语法直接忽略
|
|
if (params.inUrl) {
|
|
degradedParams.inUrl = undefined
|
|
warnings.push('Startpage不支持inurl语法')
|
|
}
|
|
if (params.inText) {
|
|
degradedParams.inText = undefined
|
|
warnings.push('Startpage不支持intext语法')
|
|
}
|
|
if (params.wildcardQuery) {
|
|
degradedParams.wildcardQuery = undefined
|
|
warnings.push('Startpage不支持通配符语法')
|
|
}
|
|
if (params.allInTitle) {
|
|
degradedParams.allInTitle = undefined
|
|
warnings.push('Startpage不支持allintitle语法')
|
|
}
|
|
if (params.relatedSite) {
|
|
degradedParams.relatedSite = undefined
|
|
warnings.push('Startpage不支持related语法')
|
|
}
|
|
if (params.cacheSite) {
|
|
degradedParams.cacheSite = undefined
|
|
warnings.push('Startpage不支持cache语法')
|
|
}
|
|
if (params.numberRange) {
|
|
degradedParams.numberRange = undefined
|
|
warnings.push('Startpage不支持数字范围语法')
|
|
}
|
|
|
|
if (warnings.length > 0) {
|
|
console.warn(`[Startpage] 语法降级:`, warnings.join('; '))
|
|
}
|
|
|
|
return degradedParams
|
|
}
|
|
|
|
// 日期格式: before:YYYY-MM-DD after:YYYY-MM-DD (与 Google 一致)
|
|
private buildDateFilter(dateRange: { from: string; to: string }): string {
|
|
const { from, to } = dateRange
|
|
if (from && to) {
|
|
return `after:${this.formatDate(from)} before:${this.formatDate(to)}`
|
|
} else if (from) {
|
|
return `after:${this.formatDate(from)}`
|
|
} else if (to) {
|
|
return `before:${this.formatDate(to)}`
|
|
}
|
|
return ''
|
|
}
|
|
|
|
getSupportedFeatures(): UIFeatureType[] {
|
|
return [
|
|
'site', 'filetype', 'intitle',
|
|
'exact_match', 'exclude', 'or_keywords',
|
|
'date_range'
|
|
]
|
|
}
|
|
|
|
getFeatureGroups(): EngineFeatureGroups {
|
|
return {
|
|
location: ['site', 'filetype', 'intitle'],
|
|
precision: ['exact_match'],
|
|
logic: ['exclude', 'or_keywords'],
|
|
range: ['date_range']
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**关键实现细节**:
|
|
- **baseUrl**: `https://www.startpage.com/sp/search`
|
|
- **查询参数**: `?query=` (注意不是 `?q=`)
|
|
- **语法处理**: 复用 GoogleAdapter 核心逻辑,但过滤不支持的语法
|
|
- **日期格式**: `before:` / `after:` (与 Google 一致)
|
|
- **语法降级**: 大量不支持语法需要降级处理
|
|
|
|
##### 3.2.3 Ecosia Adapter
|
|
|
|
**文件路径**: `/Users/lhly/chromeex/ssp/src/services/adapters/ecosia.ts`
|
|
|
|
**实现规格**:
|
|
```typescript
|
|
/**
|
|
* Ecosia 搜索引擎适配器
|
|
* 基于 Google/Bing 混合语法实现 (环保搜索)
|
|
*/
|
|
export class EcosiaAdapter implements SearchEngineAdapter {
|
|
getName(): string {
|
|
return 'Ecosia'
|
|
}
|
|
|
|
getBaseUrl(): string {
|
|
return 'https://www.ecosia.org/search'
|
|
}
|
|
|
|
// 查询参数格式: ?q={query}
|
|
buildQuery(params: SearchParams): string {
|
|
const baseUrl = this.getBaseUrl()
|
|
const query = this.buildSearchQuery(params)
|
|
return `${baseUrl}?q=${encodeURIComponent(query)}`
|
|
}
|
|
|
|
// 支持的语法 (Google/Bing 基础子集)
|
|
getSupportedSyntax(): SyntaxType[] {
|
|
return [
|
|
'site',
|
|
'filetype',
|
|
'exact',
|
|
'exclude',
|
|
'or'
|
|
// 官方文档仅明确支持这些基础操作符
|
|
// 不支持: date_range, intitle, inurl, intext 等高级语法
|
|
]
|
|
}
|
|
|
|
// 多关键词支持 (与 Google/Bing 一致)
|
|
// - sites: 支持 OR 组合
|
|
// - fileTypes: 支持 OR 组合
|
|
// - exactMatches: 原生并列支持
|
|
|
|
// 语法降级
|
|
degradeSyntax(params: SearchParams): SearchParams {
|
|
const degradedParams = { ...params }
|
|
const warnings: string[] = []
|
|
|
|
// Ecosia 仅支持基础语法
|
|
const unsupportedFields = [
|
|
'dateRange', 'inTitle', 'inUrl', 'inText', 'allInTitle',
|
|
'numberRange', 'wildcardQuery', 'relatedSite', 'cacheSite'
|
|
]
|
|
|
|
unsupportedFields.forEach(field => {
|
|
if (params[field as keyof SearchParams]) {
|
|
(degradedParams as any)[field] = undefined
|
|
warnings.push(`Ecosia不支持${field}语法`)
|
|
}
|
|
})
|
|
|
|
if (warnings.length > 0) {
|
|
console.warn(`[Ecosia] 语法降级:`, warnings.join('; '))
|
|
}
|
|
|
|
return degradedParams
|
|
}
|
|
|
|
getSupportedFeatures(): UIFeatureType[] {
|
|
return [
|
|
'site', 'filetype',
|
|
'exact_match', 'exclude', 'or_keywords'
|
|
]
|
|
}
|
|
|
|
getFeatureGroups(): EngineFeatureGroups {
|
|
return {
|
|
location: ['site', 'filetype'],
|
|
precision: ['exact_match'],
|
|
logic: ['exclude', 'or_keywords']
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**关键实现细节**:
|
|
- **baseUrl**: `https://www.ecosia.org/search`
|
|
- **查询参数**: `?q=`
|
|
- **语法处理**: 仅实现基础语法 (site, filetype, exact, exclude, OR)
|
|
- **语法降级**: 大量高级语法不支持,需要全部降级
|
|
|
|
#### Phase 2: 区域市场引擎 (3个)
|
|
|
|
##### 3.2.4 Naver Adapter
|
|
|
|
**文件路径**: `/Users/lhly/chromeex/ssp/src/services/adapters/naver.ts`
|
|
|
|
**实现规格**:
|
|
```typescript
|
|
/**
|
|
* Naver 搜索引擎适配器
|
|
* 韩国市场第一大搜索引擎
|
|
*/
|
|
export class NaverAdapter implements SearchEngineAdapter {
|
|
getName(): string {
|
|
return 'Naver' // 不翻译为韩语,仅中英文
|
|
}
|
|
|
|
getBaseUrl(): string {
|
|
return 'https://search.naver.com/search.naver'
|
|
}
|
|
|
|
// 查询参数格式: ?query={query}
|
|
buildQuery(params: SearchParams): string {
|
|
const baseUrl = this.getBaseUrl()
|
|
const query = this.buildSearchQuery(params)
|
|
return `${baseUrl}?query=${encodeURIComponent(query)}`
|
|
}
|
|
|
|
// 支持的语法 (基础子集)
|
|
getSupportedSyntax(): SyntaxType[] {
|
|
return [
|
|
'site',
|
|
'filetype',
|
|
'exact',
|
|
'exclude'
|
|
// Naver 官方文档支持有限,不支持 OR, intitle, inurl 等高级语法
|
|
]
|
|
}
|
|
|
|
// 多关键词支持 (有限)
|
|
// - sites: 不支持 OR 组合 (单个 site: 仅)
|
|
// - fileTypes: 支持基础文件类型 (pdf, doc, xls, ppt, hwp)
|
|
// - exactMatches: 原生并列支持
|
|
|
|
// 语法降级
|
|
degradeSyntax(params: SearchParams): SearchParams {
|
|
const degradedParams = { ...params }
|
|
const warnings: string[] = []
|
|
|
|
// Naver 不支持多站点 OR 组合
|
|
if (params.sites && params.sites.length > 1) {
|
|
degradedParams.sites = [params.sites[0]] // 仅保留第一个
|
|
warnings.push('Naver不支持多站点OR组合,仅使用第一个站点')
|
|
}
|
|
|
|
// Naver 不支持多文件类型 OR 组合
|
|
if (params.fileTypes && params.fileTypes.length > 1) {
|
|
degradedParams.fileTypes = [params.fileTypes[0]]
|
|
warnings.push('Naver不支持多文件类型OR组合,仅使用第一个类型')
|
|
}
|
|
|
|
// 不支持的高级语法
|
|
const unsupportedFields = [
|
|
'orKeywords', 'dateRange', 'inTitle', 'inUrl', 'inText',
|
|
'allInTitle', 'numberRange', 'wildcardQuery', 'relatedSite', 'cacheSite'
|
|
]
|
|
|
|
unsupportedFields.forEach(field => {
|
|
if (params[field as keyof SearchParams]) {
|
|
(degradedParams as any)[field] = undefined
|
|
warnings.push(`Naver不支持${field}语法`)
|
|
}
|
|
})
|
|
|
|
if (warnings.length > 0) {
|
|
console.warn(`[Naver] 语法降级:`, warnings.join('; '))
|
|
}
|
|
|
|
return degradedParams
|
|
}
|
|
|
|
// 文件类型支持: pdf, doc, xls, ppt, hwp (韩国 Hancom 文档格式)
|
|
private getSupportedFileTypes(): string[] {
|
|
return ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'hwp']
|
|
}
|
|
|
|
getSupportedFeatures(): UIFeatureType[] {
|
|
return [
|
|
'site', 'filetype',
|
|
'exact_match', 'exclude'
|
|
]
|
|
}
|
|
|
|
getFeatureGroups(): EngineFeatureGroups {
|
|
return {
|
|
location: ['site', 'filetype'],
|
|
precision: ['exact_match'],
|
|
logic: ['exclude']
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**关键实现细节**:
|
|
- **baseUrl**: `https://search.naver.com/search.naver`
|
|
- **查询参数**: `?query=`
|
|
- **语法处理**: 仅基础语法,不支持 OR 组合
|
|
- **特殊文件类型**: 支持韩国 HWP 格式 (Hancom Office)
|
|
- **多关键词限制**: 不支持 OR 组合,降级为单个
|
|
|
|
##### 3.2.5 Sogou Adapter
|
|
|
|
**文件路径**: `/Users/lhly/chromeex/ssp/src/services/adapters/sogou.ts`
|
|
|
|
**实现规格**:
|
|
```typescript
|
|
/**
|
|
* Sogou 搜索引擎适配器
|
|
* 中国第二大搜索引擎
|
|
*/
|
|
export class SogouAdapter implements SearchEngineAdapter {
|
|
getName(): string {
|
|
return '搜狗'
|
|
}
|
|
|
|
getBaseUrl(): string {
|
|
return 'https://www.sogou.com/web'
|
|
}
|
|
|
|
// 查询参数格式: ?query={query}
|
|
buildQuery(params: SearchParams): string {
|
|
const baseUrl = this.getBaseUrl()
|
|
const query = this.buildSearchQuery(params)
|
|
return `${baseUrl}?query=${encodeURIComponent(query)}`
|
|
}
|
|
|
|
// 支持的语法 (参考 Baidu,部分支持)
|
|
getSupportedSyntax(): SyntaxType[] {
|
|
return [
|
|
'site',
|
|
'filetype',
|
|
'exact',
|
|
'intitle',
|
|
'inurl',
|
|
'exclude',
|
|
'or'
|
|
// 不支持: date_range, intext, allintitle, number_range, wildcard, related, cache
|
|
]
|
|
}
|
|
|
|
// 多关键词支持 (部分支持)
|
|
// - sites: 支持 OR 组合
|
|
// - fileTypes: 支持 OR 组合 (pdf, doc, xls, ppt, txt)
|
|
// - exactMatches: 原生并列支持
|
|
|
|
// 语法降级
|
|
degradeSyntax(params: SearchParams): SearchParams {
|
|
const degradedParams = { ...params }
|
|
const warnings: string[] = []
|
|
|
|
// Sogou 不支持的语法
|
|
const unsupportedFields = [
|
|
'dateRange', 'inText', 'allInTitle', 'numberRange',
|
|
'wildcardQuery', 'relatedSite', 'cacheSite'
|
|
]
|
|
|
|
unsupportedFields.forEach(field => {
|
|
if (params[field as keyof SearchParams]) {
|
|
(degradedParams as any)[field] = undefined
|
|
warnings.push(`Sogou不支持${field}语法`)
|
|
}
|
|
})
|
|
|
|
if (warnings.length > 0) {
|
|
console.warn(`[Sogou] 语法降级:`, warnings.join('; '))
|
|
}
|
|
|
|
return degradedParams
|
|
}
|
|
|
|
// 文件类型支持: pdf, doc, docx, xls, xlsx, ppt, pptx, txt
|
|
private getSupportedFileTypes(): string[] {
|
|
return ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt']
|
|
}
|
|
|
|
getSupportedFeatures(): UIFeatureType[] {
|
|
return [
|
|
'site', 'filetype', 'intitle', 'inurl',
|
|
'exact_match', 'exclude', 'or_keywords'
|
|
]
|
|
}
|
|
|
|
getFeatureGroups(): EngineFeatureGroups {
|
|
return {
|
|
location: ['site', 'filetype', 'intitle', 'inurl'],
|
|
precision: ['exact_match'],
|
|
logic: ['exclude', 'or_keywords']
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**关键实现细节**:
|
|
- **baseUrl**: `https://www.sogou.com/web`
|
|
- **查询参数**: `?query=`
|
|
- **语法处理**: 参考 BaiduAdapter,支持中等复杂度语法
|
|
- **中文优化**: 支持中文分词和拼音搜索 (引擎内部处理)
|
|
- **多关键词支持**: 支持 OR 组合
|
|
|
|
##### 3.2.6 360 Search Adapter
|
|
|
|
**文件路径**: `/Users/lhly/chromeex/ssp/src/services/adapters/so360.ts`
|
|
|
|
**实现规格**:
|
|
```typescript
|
|
/**
|
|
* 360搜索引擎适配器
|
|
* 中国市场第三大搜索引擎
|
|
*/
|
|
export class So360Adapter implements SearchEngineAdapter {
|
|
getName(): string {
|
|
return '360搜索'
|
|
}
|
|
|
|
getBaseUrl(): string {
|
|
return 'https://www.so.com/s'
|
|
}
|
|
|
|
// 查询参数格式: ?q={query}
|
|
buildQuery(params: SearchParams): string {
|
|
const baseUrl = this.getBaseUrl()
|
|
const query = this.buildSearchQuery(params)
|
|
return `${baseUrl}?q=${encodeURIComponent(query)}`
|
|
}
|
|
|
|
// 支持的语法 (基础子集)
|
|
getSupportedSyntax(): SyntaxType[] {
|
|
return [
|
|
'site',
|
|
'filetype',
|
|
'exact',
|
|
'intitle',
|
|
'exclude'
|
|
// 不支持: OR, inurl, intext, date_range 等高级语法
|
|
]
|
|
}
|
|
|
|
// 多关键词支持 (有限)
|
|
// - sites: 不支持 OR 组合
|
|
// - fileTypes: 不支持 OR 组合
|
|
// - exactMatches: 原生并列支持
|
|
|
|
// 语法降级
|
|
degradeSyntax(params: SearchParams): SearchParams {
|
|
const degradedParams = { ...params }
|
|
const warnings: string[] = []
|
|
|
|
// 360搜索 不支持多站点 OR 组合
|
|
if (params.sites && params.sites.length > 1) {
|
|
degradedParams.sites = [params.sites[0]]
|
|
warnings.push('360搜索不支持多站点OR组合,仅使用第一个站点')
|
|
}
|
|
|
|
// 360搜索 不支持多文件类型 OR 组合
|
|
if (params.fileTypes && params.fileTypes.length > 1) {
|
|
degradedParams.fileTypes = [params.fileTypes[0]]
|
|
warnings.push('360搜索不支持多文件类型OR组合,仅使用第一个类型')
|
|
}
|
|
|
|
// 不支持的高级语法
|
|
const unsupportedFields = [
|
|
'orKeywords', 'dateRange', 'inUrl', 'inText', 'allInTitle',
|
|
'numberRange', 'wildcardQuery', 'relatedSite', 'cacheSite'
|
|
]
|
|
|
|
unsupportedFields.forEach(field => {
|
|
if (params[field as keyof SearchParams]) {
|
|
(degradedParams as any)[field] = undefined
|
|
warnings.push(`360搜索不支持${field}语法`)
|
|
}
|
|
})
|
|
|
|
if (warnings.length > 0) {
|
|
console.warn(`[360搜索] 语法降级:`, warnings.join('; '))
|
|
}
|
|
|
|
return degradedParams
|
|
}
|
|
|
|
getSupportedFeatures(): UIFeatureType[] {
|
|
return [
|
|
'site', 'filetype', 'intitle',
|
|
'exact_match', 'exclude'
|
|
]
|
|
}
|
|
|
|
getFeatureGroups(): EngineFeatureGroups {
|
|
return {
|
|
location: ['site', 'filetype', 'intitle'],
|
|
precision: ['exact_match'],
|
|
logic: ['exclude']
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**关键实现细节**:
|
|
- **baseUrl**: `https://www.so.com/s`
|
|
- **查询参数**: `?q=`
|
|
- **语法处理**: 基础语法支持,类似 Naver
|
|
- **多关键词限制**: 不支持 OR 组合,降级为单个
|
|
- **中文优化**: 支持中文内容优化 (引擎内部处理)
|
|
|
|
#### Phase 3: 欧洲市场引擎 (1个)
|
|
|
|
##### 3.2.7 Qwant Adapter
|
|
|
|
**文件路径**: `/Users/lhly/chromeex/ssp/src/services/adapters/qwant.ts`
|
|
|
|
**实现规格**:
|
|
```typescript
|
|
/**
|
|
* Qwant 搜索引擎适配器
|
|
* 法国隐私友好搜索引擎
|
|
*/
|
|
export class QwantAdapter implements SearchEngineAdapter {
|
|
getName(): string {
|
|
return 'Qwant'
|
|
}
|
|
|
|
getBaseUrl(): string {
|
|
return 'https://www.qwant.com/'
|
|
}
|
|
|
|
// 查询参数格式: ?q={query}
|
|
buildQuery(params: SearchParams): string {
|
|
const baseUrl = this.getBaseUrl()
|
|
const query = this.buildSearchQuery(params)
|
|
return `${baseUrl}?q=${encodeURIComponent(query)}`
|
|
}
|
|
|
|
// 支持的语法 (基础子集)
|
|
getSupportedSyntax(): SyntaxType[] {
|
|
return [
|
|
'site',
|
|
'filetype',
|
|
'exact'
|
|
// Qwant 官方文档显示支持极其有限
|
|
// 不支持: exclude, OR 及所有高级语法
|
|
]
|
|
}
|
|
|
|
// 多关键词支持 (不支持)
|
|
// - sites: 不支持 OR 组合
|
|
// - fileTypes: 不支持 OR 组合
|
|
// - exactMatches: 原生并列支持
|
|
|
|
// 语法降级
|
|
degradeSyntax(params: SearchParams): SearchParams {
|
|
const degradedParams = { ...params }
|
|
const warnings: string[] = []
|
|
|
|
// Qwant 不支持多站点 OR 组合
|
|
if (params.sites && params.sites.length > 1) {
|
|
degradedParams.sites = [params.sites[0]]
|
|
warnings.push('Qwant不支持多站点OR组合,仅使用第一个站点')
|
|
}
|
|
|
|
// Qwant 不支持多文件类型 OR 组合
|
|
if (params.fileTypes && params.fileTypes.length > 1) {
|
|
degradedParams.fileTypes = [params.fileTypes[0]]
|
|
warnings.push('Qwant不支持多文件类型OR组合,仅使用第一个类型')
|
|
}
|
|
|
|
// 不支持的所有高级语法
|
|
const unsupportedFields = [
|
|
'excludeWords', 'orKeywords', 'dateRange', 'inTitle', 'inUrl',
|
|
'inText', 'allInTitle', 'numberRange', 'wildcardQuery',
|
|
'relatedSite', 'cacheSite'
|
|
]
|
|
|
|
unsupportedFields.forEach(field => {
|
|
if (params[field as keyof SearchParams]) {
|
|
(degradedParams as any)[field] = undefined
|
|
warnings.push(`Qwant不支持${field}语法`)
|
|
}
|
|
})
|
|
|
|
if (warnings.length > 0) {
|
|
console.warn(`[Qwant] 语法降级:`, warnings.join('; '))
|
|
}
|
|
|
|
return degradedParams
|
|
}
|
|
|
|
getSupportedFeatures(): UIFeatureType[] {
|
|
return [
|
|
'site', 'filetype',
|
|
'exact_match'
|
|
]
|
|
}
|
|
|
|
getFeatureGroups(): EngineFeatureGroups {
|
|
return {
|
|
location: ['site', 'filetype'],
|
|
precision: ['exact_match']
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**关键实现细节**:
|
|
- **baseUrl**: `https://www.qwant.com/`
|
|
- **查询参数**: `?q=`
|
|
- **语法处理**: 最小语法支持 (仅 site, filetype, exact)
|
|
- **多关键词限制**: 不支持 OR 组合
|
|
- **Qwick 快捷方式**: 可选实现 (如 !w Wikipedia),暂不实现
|
|
|
|
### 3.3 Type Definitions
|
|
|
|
**文件路径**: `/Users/lhly/chromeex/ssp/src/types/index.ts`
|
|
|
|
**修改内容**:
|
|
```typescript
|
|
// 第 2 行,修改 SearchEngine 类型定义
|
|
export type SearchEngine =
|
|
| 'baidu'
|
|
| 'google'
|
|
| 'bing'
|
|
| 'twitter'
|
|
| 'duckduckgo'
|
|
| 'brave'
|
|
| 'yandex'
|
|
| 'reddit'
|
|
| 'github'
|
|
| 'stackoverflow'
|
|
// 🔥 新增 7 个搜索引擎
|
|
| 'yahoo'
|
|
| 'startpage'
|
|
| 'ecosia'
|
|
| 'qwant'
|
|
| 'naver'
|
|
| 'sogou'
|
|
| 'so360';
|
|
```
|
|
|
|
### 3.4 Factory Registration
|
|
|
|
**文件路径**: `/Users/lhly/chromeex/ssp/src/services/adapters/factory.ts`
|
|
|
|
**修改内容**:
|
|
|
|
1. **Import 语句** (第 1-11 行后新增):
|
|
```typescript
|
|
import { YahooAdapter } from './yahoo'
|
|
import { StartpageAdapter } from './startpage'
|
|
import { EcosiaAdapter } from './ecosia'
|
|
import { QwantAdapter } from './qwant'
|
|
import { NaverAdapter } from './naver'
|
|
import { SogouAdapter } from './sogou'
|
|
import { So360Adapter } from './so360'
|
|
```
|
|
|
|
2. **createAdapter 方法** (第 39-63 行,switch case 新增):
|
|
```typescript
|
|
private static createAdapter(engine: SearchEngine): SearchEngineAdapter {
|
|
switch (engine) {
|
|
// 现有引擎 ...
|
|
case 'stackoverflow':
|
|
return new StackOverflowAdapter()
|
|
|
|
// 🔥 新增 7 个引擎适配器
|
|
case 'yahoo':
|
|
return new YahooAdapter()
|
|
case 'startpage':
|
|
return new StartpageAdapter()
|
|
case 'ecosia':
|
|
return new EcosiaAdapter()
|
|
case 'qwant':
|
|
return new QwantAdapter()
|
|
case 'naver':
|
|
return new NaverAdapter()
|
|
case 'sogou':
|
|
return new SogouAdapter()
|
|
case 'so360':
|
|
return new So360Adapter()
|
|
|
|
default:
|
|
throw new Error(`不支持的搜索引擎: ${engine}`)
|
|
}
|
|
}
|
|
```
|
|
|
|
3. **getSupportedEngines 方法** (第 69-71 行,数组新增):
|
|
```typescript
|
|
static getSupportedEngines(): SearchEngine[] {
|
|
return [
|
|
'baidu', 'google', 'bing', 'twitter', 'duckduckgo',
|
|
'brave', 'yandex', 'reddit', 'github', 'stackoverflow',
|
|
// 🔥 新增 7 个引擎
|
|
'yahoo', 'startpage', 'ecosia', 'qwant',
|
|
'naver', 'sogou', 'so360'
|
|
]
|
|
}
|
|
```
|
|
|
|
### 3.5 Internationalization (i18n)
|
|
|
|
**文件路径**: `/Users/lhly/chromeex/ssp/src/i18n/translations.ts`
|
|
|
|
**修改内容** (在第 16 行后新增):
|
|
|
|
#### 3.5.1 引擎名称翻译
|
|
|
|
```typescript
|
|
const translations: Record<Language, Record<string, string>> = {
|
|
'zh-CN': {
|
|
// 现有引擎 ...
|
|
'common.searchEngines.stackoverflow': 'Stack Overflow',
|
|
|
|
// 🔥 新增 7 个引擎名称 (中文)
|
|
'common.searchEngines.yahoo': 'Yahoo搜索',
|
|
'common.searchEngines.startpage': 'Startpage',
|
|
'common.searchEngines.ecosia': 'Ecosia',
|
|
'common.searchEngines.qwant': 'Qwant',
|
|
'common.searchEngines.naver': 'Naver',
|
|
'common.searchEngines.sogou': '搜狗',
|
|
'common.searchEngines.so360': '360搜索',
|
|
|
|
// 其余翻译 ...
|
|
},
|
|
'en-US': {
|
|
// 现有引擎 ...
|
|
'common.searchEngines.stackoverflow': 'Stack Overflow',
|
|
|
|
// 🔥 新增 7 个引擎名称 (英文)
|
|
'common.searchEngines.yahoo': 'Yahoo Search',
|
|
'common.searchEngines.startpage': 'Startpage',
|
|
'common.searchEngines.ecosia': 'Ecosia',
|
|
'common.searchEngines.qwant': 'Qwant',
|
|
'common.searchEngines.naver': 'Naver',
|
|
'common.searchEngines.sogou': 'Sogou',
|
|
'common.searchEngines.so360': '360 Search',
|
|
|
|
// 其余翻译 ...
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 3.5.2 验证消息翻译
|
|
|
|
**新增翻译键** (在适当位置添加):
|
|
|
|
```typescript
|
|
'zh-CN': {
|
|
// ... 现有翻译 ...
|
|
|
|
// 🔥 新增适配器验证消息
|
|
'adapter.validation.unsupportedSyntax': '{engine}不支持{syntax}语法,该参数将被忽略',
|
|
'adapter.validation.unsupportedMultiKeyword': '{engine}不支持多关键词OR组合,仅使用第一个',
|
|
'adapter.validation.syntaxDegraded': '{engine}不支持部分高级语法,已自动降级',
|
|
'adapter.validation.queryLengthExceeded': '查询长度超过{engine}的限制({max}字符)',
|
|
|
|
// ... 其余翻译 ...
|
|
},
|
|
'en-US': {
|
|
// ... 现有翻译 ...
|
|
|
|
// 🔥 新增适配器验证消息
|
|
'adapter.validation.unsupportedSyntax': '{engine} does not support {syntax} syntax, parameter ignored',
|
|
'adapter.validation.unsupportedMultiKeyword': '{engine} does not support multi-keyword OR combination, using first one only',
|
|
'adapter.validation.syntaxDegraded': '{engine} does not support some advanced syntax, automatically degraded',
|
|
'adapter.validation.queryLengthExceeded': 'Query length exceeds {engine} limit ({max} characters)',
|
|
|
|
// ... 其余翻译 ...
|
|
}
|
|
```
|
|
|
|
### 3.6 Configuration Changes
|
|
|
|
**无需配置文件修改** - 所有配置通过代码硬编码
|
|
|
|
### 3.7 Query Length Limits
|
|
|
|
**统一查询长度限制** (在各适配器的 validateParams 方法中):
|
|
|
|
```typescript
|
|
// 所有引擎统一使用 180 字符限制 (最保守策略)
|
|
const MAX_QUERY_LENGTH = 180
|
|
|
|
async validateParams(params: SearchParams): Promise<ValidationResult> {
|
|
const warnings: string[] = []
|
|
const language = await getCurrentLanguage()
|
|
const t = (key: string, vars?) => translate(language, key, vars)
|
|
|
|
const fullQuery = this.buildSearchQuery(params)
|
|
if (fullQuery.length > MAX_QUERY_LENGTH) {
|
|
warnings.push(
|
|
t('adapter.validation.queryLengthExceeded', {
|
|
engine: this.getName(),
|
|
max: MAX_QUERY_LENGTH.toString()
|
|
})
|
|
)
|
|
}
|
|
|
|
return { isValid: true, warnings }
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Implementation Sequence
|
|
|
|
### Phase 1: 兼容性引擎 (预计 2-3 小时)
|
|
|
|
#### Step 1.1: Yahoo Adapter 实现
|
|
- [ ] 创建 `/src/services/adapters/yahoo.ts`
|
|
- [ ] 复用 BingAdapter 核心逻辑
|
|
- [ ] 修改 baseUrl 为 `https://search.yahoo.com/search`
|
|
- [ ] 修改查询参数为 `?p=`
|
|
- [ ] 实现 degradeSyntax (处理 cache)
|
|
- [ ] 创建 `/src/services/adapters/yahoo.test.ts`
|
|
- [ ] 编写核心逻辑单元测试
|
|
|
|
#### Step 1.2: Startpage Adapter 实现
|
|
- [ ] 创建 `/src/services/adapters/startpage.ts`
|
|
- [ ] 复用 GoogleAdapter 核心逻辑
|
|
- [ ] 修改 baseUrl 为 `https://www.startpage.com/sp/search`
|
|
- [ ] 修改查询参数为 `?query=`
|
|
- [ ] 实现 degradeSyntax (处理多个不支持语法)
|
|
- [ ] 创建 `/src/services/adapters/startpage.test.ts`
|
|
- [ ] 编写核心逻辑单元测试
|
|
|
|
#### Step 1.3: Ecosia Adapter 实现
|
|
- [ ] 创建 `/src/services/adapters/ecosia.ts`
|
|
- [ ] 实现基础语法支持 (site, filetype, exact, exclude, OR)
|
|
- [ ] 修改 baseUrl 为 `https://www.ecosia.org/search`
|
|
- [ ] 实现 degradeSyntax (处理所有高级语法)
|
|
- [ ] 创建 `/src/services/adapters/ecosia.test.ts`
|
|
- [ ] 编写核心逻辑单元测试
|
|
|
|
#### Step 1.4: Phase 1 集成
|
|
- [ ] 更新 `src/types/index.ts` (添加 yahoo, startpage, ecosia)
|
|
- [ ] 更新 `src/services/adapters/factory.ts` (注册 3 个适配器)
|
|
- [ ] 更新 `src/i18n/translations.ts` (添加引擎名称翻译)
|
|
- [ ] 手动测试 3 个引擎的搜索功能
|
|
- [ ] 验证语法降级警告正确显示
|
|
|
|
### Phase 2: 区域市场引擎 (预计 3-4 小时)
|
|
|
|
#### Step 2.1: Naver Adapter 实现
|
|
- [ ] 创建 `/src/services/adapters/naver.ts`
|
|
- [ ] 实现基础语法 (site, filetype, exact, exclude)
|
|
- [ ] 修改 baseUrl 为 `https://search.naver.com/search.naver`
|
|
- [ ] 实现 degradeSyntax (禁用 OR 组合,处理高级语法)
|
|
- [ ] 添加 HWP 文件类型支持
|
|
- [ ] 创建 `/src/services/adapters/naver.test.ts`
|
|
- [ ] 编写核心逻辑单元测试
|
|
|
|
#### Step 2.2: Sogou Adapter 实现
|
|
- [ ] 创建 `/src/services/adapters/sogou.ts`
|
|
- [ ] 参考 BaiduAdapter,实现中等复杂度语法
|
|
- [ ] 修改 baseUrl 为 `https://www.sogou.com/web`
|
|
- [ ] 修改查询参数为 `?query=`
|
|
- [ ] 实现 degradeSyntax (处理不支持的高级语法)
|
|
- [ ] 创建 `/src/services/adapters/sogou.test.ts`
|
|
- [ ] 编写核心逻辑单元测试
|
|
|
|
#### Step 2.3: 360 Search Adapter 实现
|
|
- [ ] 创建 `/src/services/adapters/so360.ts`
|
|
- [ ] 实现基础语法 (site, filetype, exact, intitle, exclude)
|
|
- [ ] 修改 baseUrl 为 `https://www.so.com/s`
|
|
- [ ] 实现 degradeSyntax (禁用 OR 组合,处理高级语法)
|
|
- [ ] 创建 `/src/services/adapters/so360.test.ts`
|
|
- [ ] 编写核心逻辑单元测试
|
|
|
|
#### Step 2.4: Phase 2 集成
|
|
- [ ] 更新 `src/types/index.ts` (添加 naver, sogou, so360)
|
|
- [ ] 更新 `src/services/adapters/factory.ts` (注册 3 个适配器)
|
|
- [ ] 更新 `src/i18n/translations.ts` (添加引擎名称翻译)
|
|
- [ ] 手动测试 3 个引擎的搜索功能
|
|
- [ ] 验证中文内容搜索正常工作
|
|
|
|
### Phase 3: 欧洲市场引擎 (预计 1-2 小时)
|
|
|
|
#### Step 3.1: Qwant Adapter 实现
|
|
- [ ] 创建 `/src/services/adapters/qwant.ts`
|
|
- [ ] 实现最小语法支持 (site, filetype, exact)
|
|
- [ ] 修改 baseUrl 为 `https://www.qwant.com/`
|
|
- [ ] 实现 degradeSyntax (处理所有高级语法)
|
|
- [ ] 创建 `/src/services/adapters/qwant.test.ts`
|
|
- [ ] 编写核心逻辑单元测试
|
|
|
|
#### Step 3.2: Phase 3 集成
|
|
- [ ] 更新 `src/types/index.ts` (添加 qwant)
|
|
- [ ] 更新 `src/services/adapters/factory.ts` (注册 qwant)
|
|
- [ ] 更新 `src/i18n/translations.ts` (添加引擎名称翻译)
|
|
- [ ] 手动测试 Qwant 搜索功能
|
|
|
|
### Final Integration (预计 1 小时)
|
|
|
|
#### Step 4.1: 完整性验证
|
|
- [ ] 运行所有单元测试 (`npm run test`)
|
|
- [ ] 运行类型检查 (`npm run type-check`)
|
|
- [ ] 运行 ESLint (`npm run lint`)
|
|
- [ ] 手动测试所有 17 个引擎 (10 + 7)
|
|
|
|
#### Step 4.2: 国际化验证
|
|
- [ ] 切换到中文,验证所有引擎名称正确显示
|
|
- [ ] 切换到英文,验证所有引擎名称正确显示
|
|
- [ ] 验证语法降级警告消息的中英文显示
|
|
|
|
#### Step 4.3: 功能验证
|
|
- [ ] 测试多关键词 OR 组合 (在支持的引擎中)
|
|
- [ ] 测试语法降级 (在不支持的引擎中)
|
|
- [ ] 测试查询长度警告
|
|
- [ ] 测试引擎切换功能
|
|
|
|
#### Step 4.4: 文档更新
|
|
- [ ] 更新 `README.md` (如果需要)
|
|
- [ ] 更新版本号至 1.9.0
|
|
- [ ] 生成 CHANGELOG
|
|
|
|
---
|
|
|
|
## 5. Test Specification
|
|
|
|
### 5.1 Unit Test Structure
|
|
|
|
**测试文件命名**: `{adapter-name}.test.ts`
|
|
|
|
**测试框架**: Jest + @testing-library
|
|
|
|
### 5.2 Core Test Cases
|
|
|
|
#### 5.2.1 Yahoo Adapter Tests
|
|
|
|
**文件**: `/src/services/adapters/yahoo.test.ts`
|
|
|
|
```typescript
|
|
import { YahooAdapter } from './yahoo'
|
|
import type { SearchParams } from '@/types'
|
|
|
|
describe('YahooAdapter', () => {
|
|
let adapter: YahooAdapter
|
|
|
|
beforeEach(() => {
|
|
adapter = new YahooAdapter()
|
|
})
|
|
|
|
describe('基础功能', () => {
|
|
test('应返回正确的引擎名称', () => {
|
|
expect(adapter.getName()).toBe('雅虎')
|
|
})
|
|
|
|
test('应返回正确的基础URL', () => {
|
|
expect(adapter.getBaseUrl()).toBe('https://search.yahoo.com/search')
|
|
})
|
|
})
|
|
|
|
describe('URL构建', () => {
|
|
test('应构建基础搜索URL', () => {
|
|
const params: SearchParams = {
|
|
keyword: 'test query',
|
|
engine: 'yahoo'
|
|
}
|
|
const url = adapter.buildQuery(params)
|
|
expect(url).toContain('search.yahoo.com/search')
|
|
expect(url).toContain('?p=')
|
|
expect(url).toContain('test%20query')
|
|
})
|
|
|
|
test('应支持site语法', () => {
|
|
const params: SearchParams = {
|
|
keyword: 'test',
|
|
engine: 'yahoo',
|
|
site: 'example.com'
|
|
}
|
|
const url = adapter.buildQuery(params)
|
|
expect(url).toContain('site%3Aexample.com')
|
|
})
|
|
|
|
test('应支持多站点OR组合', () => {
|
|
const params: SearchParams = {
|
|
keyword: 'test',
|
|
engine: 'yahoo',
|
|
sites: ['example.com', 'test.com']
|
|
}
|
|
const url = adapter.buildQuery(params)
|
|
expect(url).toContain('site%3Aexample.com')
|
|
expect(url).toContain('OR')
|
|
expect(url).toContain('site%3Atest.com')
|
|
})
|
|
|
|
test('应支持filetype语法', () => {
|
|
const params: SearchParams = {
|
|
keyword: 'test',
|
|
engine: 'yahoo',
|
|
fileType: 'pdf'
|
|
}
|
|
const url = adapter.buildQuery(params)
|
|
expect(url).toContain('filetype%3Apdf')
|
|
})
|
|
|
|
test('应支持多文件类型OR组合', () => {
|
|
const params: SearchParams = {
|
|
keyword: 'test',
|
|
engine: 'yahoo',
|
|
fileTypes: ['pdf', 'doc']
|
|
}
|
|
const url = adapter.buildQuery(params)
|
|
expect(url).toContain('filetype%3Apdf')
|
|
expect(url).toContain('OR')
|
|
expect(url).toContain('filetype%3Adoc')
|
|
})
|
|
})
|
|
|
|
describe('语法支持', () => {
|
|
test('应支持Bing兼容语法', () => {
|
|
const supportedSyntax = adapter.getSupportedSyntax()
|
|
expect(supportedSyntax).toContain('site')
|
|
expect(supportedSyntax).toContain('filetype')
|
|
expect(supportedSyntax).toContain('intitle')
|
|
expect(supportedSyntax).toContain('inurl')
|
|
expect(supportedSyntax).toContain('related')
|
|
})
|
|
|
|
test('不应支持cache语法', () => {
|
|
const supportedSyntax = adapter.getSupportedSyntax()
|
|
expect(supportedSyntax).not.toContain('cache')
|
|
})
|
|
})
|
|
|
|
describe('语法降级', () => {
|
|
test('应降级cache语法', () => {
|
|
const params: SearchParams = {
|
|
keyword: 'test',
|
|
engine: 'yahoo',
|
|
cacheSite: 'example.com'
|
|
}
|
|
const degraded = adapter.degradeSyntax(params)
|
|
expect(degraded.cacheSite).toBeUndefined()
|
|
})
|
|
|
|
test('应保留支持的语法', () => {
|
|
const params: SearchParams = {
|
|
keyword: 'test',
|
|
engine: 'yahoo',
|
|
site: 'example.com',
|
|
fileType: 'pdf'
|
|
}
|
|
const degraded = adapter.degradeSyntax(params)
|
|
expect(degraded.site).toBe('example.com')
|
|
expect(degraded.fileType).toBe('pdf')
|
|
})
|
|
})
|
|
})
|
|
```
|
|
|
|
#### 5.2.2 Startpage Adapter Tests
|
|
|
|
**文件**: `/src/services/adapters/startpage.test.ts`
|
|
|
|
```typescript
|
|
import { StartpageAdapter } from './startpage'
|
|
|
|
describe('StartpageAdapter', () => {
|
|
let adapter: StartpageAdapter
|
|
|
|
beforeEach(() => {
|
|
adapter = new StartpageAdapter()
|
|
})
|
|
|
|
test('应使用query参数而不是q', () => {
|
|
const params = {
|
|
keyword: 'test',
|
|
engine: 'startpage' as const
|
|
}
|
|
const url = adapter.buildQuery(params)
|
|
expect(url).toContain('?query=')
|
|
expect(url).not.toContain('?q=')
|
|
})
|
|
|
|
test('应降级不支持的inurl语法', () => {
|
|
const params = {
|
|
keyword: 'test',
|
|
engine: 'startpage' as const,
|
|
inUrl: 'blog'
|
|
}
|
|
const degraded = adapter.degradeSyntax(params)
|
|
expect(degraded.inUrl).toBeUndefined()
|
|
})
|
|
|
|
test('应支持date_range语法', () => {
|
|
const supportedSyntax = adapter.getSupportedSyntax()
|
|
expect(supportedSyntax).toContain('date_range')
|
|
expect(supportedSyntax).not.toContain('inurl')
|
|
})
|
|
})
|
|
```
|
|
|
|
#### 5.2.3 Naver Adapter Tests
|
|
|
|
**文件**: `/src/services/adapters/naver.test.ts`
|
|
|
|
```typescript
|
|
import { NaverAdapter } from './naver'
|
|
|
|
describe('NaverAdapter', () => {
|
|
let adapter: NaverAdapter
|
|
|
|
beforeEach(() => {
|
|
adapter = new NaverAdapter()
|
|
})
|
|
|
|
test('应降级多站点OR组合为单个站点', () => {
|
|
const params = {
|
|
keyword: 'test',
|
|
engine: 'naver' as const,
|
|
sites: ['site1.com', 'site2.com', 'site3.com']
|
|
}
|
|
const degraded = adapter.degradeSyntax(params)
|
|
expect(degraded.sites).toHaveLength(1)
|
|
expect(degraded.sites![0]).toBe('site1.com')
|
|
})
|
|
|
|
test('应降级多文件类型OR组合为单个类型', () => {
|
|
const params = {
|
|
keyword: 'test',
|
|
engine: 'naver' as const,
|
|
fileTypes: ['pdf', 'doc', 'xls']
|
|
}
|
|
const degraded = adapter.degradeSyntax(params)
|
|
expect(degraded.fileTypes).toHaveLength(1)
|
|
expect(degraded.fileTypes![0]).toBe('pdf')
|
|
})
|
|
|
|
test('应降级OR关键词语法', () => {
|
|
const params = {
|
|
keyword: 'test',
|
|
engine: 'naver' as const,
|
|
orKeywords: ['keyword1', 'keyword2']
|
|
}
|
|
const degraded = adapter.degradeSyntax(params)
|
|
expect(degraded.orKeywords).toBeUndefined()
|
|
})
|
|
})
|
|
```
|
|
|
|
### 5.3 Test Coverage Requirements
|
|
|
|
**必须测试的核心逻辑** (每个适配器):
|
|
1. ✅ URL 构建正确性 (baseUrl + 查询参数格式)
|
|
2. ✅ 单个语法支持 (site, filetype, exact)
|
|
3. ✅ 多关键词 OR 组合 (如果引擎支持)
|
|
4. ✅ 语法降级处理 (不支持的语法应设为 undefined)
|
|
5. ✅ getSupportedSyntax 返回正确列表
|
|
|
|
**不需要测试**:
|
|
- ❌ UI 集成测试
|
|
- ❌ E2E 测试
|
|
- ❌ 性能测试
|
|
- ❌ 国际化翻译内容测试 (手动验证即可)
|
|
|
|
---
|
|
|
|
## 6. Validation Plan
|
|
|
|
### 6.1 Unit Tests
|
|
|
|
**运行命令**: `npm run test`
|
|
|
|
**测试内容**:
|
|
- 所有适配器的 URL 构建逻辑
|
|
- 语法支持列表正确性
|
|
- 多关键词 OR 组合正确性
|
|
- 语法降级处理正确性
|
|
|
|
### 6.2 Integration Tests
|
|
|
|
**手动测试流程**:
|
|
|
|
1. **引擎切换测试**
|
|
- 打开扩展弹窗
|
|
- 依次切换到 17 个搜索引擎
|
|
- 验证引擎名称正确显示 (中英文)
|
|
|
|
2. **搜索功能测试**
|
|
- 每个引擎执行基础关键词搜索
|
|
- 验证跳转到正确的搜索引擎页面
|
|
- 验证 URL 参数格式正确
|
|
|
|
3. **语法支持测试**
|
|
- 在支持 OR 的引擎中测试多站点/多文件类型
|
|
- 在不支持 OR 的引擎中验证降级警告
|
|
- 测试高级语法在各引擎中的行为
|
|
|
|
4. **国际化测试**
|
|
- 切换语言到中文,验证所有引擎名称
|
|
- 切换语言到英文,验证所有引擎名称
|
|
- 验证警告消息的中英文显示
|
|
|
|
### 6.3 Business Logic Verification
|
|
|
|
**验收测试场景**:
|
|
|
|
| 场景 | 引擎 | 操作 | 预期结果 |
|
|
|------|------|------|---------|
|
|
| 多站点搜索 | Yahoo | 输入 2 个站点 | URL 包含 `(site:a.com OR site:b.com)` |
|
|
| 多站点降级 | Naver | 输入 2 个站点 | 仅使用第一个站点,显示警告 |
|
|
| cache 降级 | Yahoo | 输入 cache 站点 | cache 参数被忽略,显示警告 |
|
|
| 日期范围 | Startpage | 输入日期范围 | URL 包含 `before:` 和 `after:` |
|
|
| 基础语法 | Qwant | 输入 site + filetype | URL 正确构建 |
|
|
| 高级语法降级 | Qwant | 输入 intitle | intitle 被忽略,显示警告 |
|
|
| 中文搜索 | Sogou | 输入中文关键词 | 正确处理中文 URL 编码 |
|
|
|
|
---
|
|
|
|
## 7. Implementation Constraints
|
|
|
|
### 7.1 MUST Requirements
|
|
|
|
1. **Direct Implementability**: 每个适配器必须可直接编码实现,无需额外架构设计
|
|
2. **Syntax Consistency**: 语法支持必须与官方搜索引擎一致
|
|
3. **Degradation Handling**: 不支持的语法必须正确降级并发出警告
|
|
4. **i18n Completeness**: 所有用户可见文本必须有中英文翻译
|
|
5. **Test Coverage**: 核心逻辑必须有单元测试覆盖
|
|
|
|
### 7.2 MUST NOT Requirements
|
|
|
|
1. **No Over-Engineering**: 不引入复杂设计模式 (直接复用现有适配器逻辑)
|
|
2. **No Additional Languages**: 不添加中英文以外的语言支持
|
|
3. **No UI Changes**: 不修改现有 UI 组件 (使用现有下拉选择器)
|
|
4. **No Backend Services**: 不引入服务器端依赖
|
|
5. **No Breaking Changes**: 保持向后兼容,不破坏现有功能
|
|
|
|
### 7.3 Code Quality Standards
|
|
|
|
- **TypeScript**: 严格类型检查,无 any 类型
|
|
- **ESLint**: 无 linting 错误
|
|
- **Code Style**: 与现有适配器保持一致
|
|
- **Comments**: 关键逻辑添加中文注释
|
|
- **Naming**: 遵循现有命名约定
|
|
|
|
---
|
|
|
|
## 8. Risk Mitigation
|
|
|
|
### 8.1 Identified Risks
|
|
|
|
| 风险 | 可能性 | 影响 | 缓解措施 |
|
|
|------|--------|------|---------|
|
|
| 官方 URL 格式变化 | 中 | 高 | 使用 try-catch 包裹,允许降级处理 |
|
|
| 语法支持文档不准确 | 高 | 中 | 通过实际测试验证,保守策略 |
|
|
| 多关键词支持不确定 | 中 | 中 | 默认禁用,测试验证后启用 |
|
|
| 查询长度限制未知 | 低 | 低 | 统一使用 180 字符保守限制 |
|
|
| 国际化翻译遗漏 | 低 | 中 | 使用 checklist 逐项验证 |
|
|
|
|
### 8.2 Fallback Strategies
|
|
|
|
1. **URL 构建失败**: 返回基础 URL + 关键词,允许用户手动搜索
|
|
2. **语法不支持**: 降级处理,发出警告,允许搜索继续
|
|
3. **查询过长**: 仅警告,不阻止搜索
|
|
4. **验证失败**: 宽松验证策略,仅警告不阻止
|
|
|
|
---
|
|
|
|
## 9. Acceptance Criteria
|
|
|
|
### 9.1 Functional Acceptance
|
|
|
|
- [ ] 所有 7 个新引擎可正常搜索
|
|
- [ ] 多站点 OR 组合在 Yahoo, Startpage, Ecosia, Sogou 中工作
|
|
- [ ] 多文件类型 OR 组合在支持的引擎中工作
|
|
- [ ] 语法降级在 Naver, 360搜索, Qwant 中正确处理
|
|
- [ ] cache 语法在 Yahoo 中正确降级
|
|
- [ ] 查询长度超限时显示警告
|
|
|
|
### 9.2 i18n Acceptance
|
|
|
|
- [ ] 所有 7 个引擎名称有中文翻译
|
|
- [ ] 所有 7 个引擎名称有英文翻译
|
|
- [ ] 语法降级警告有中英文翻译
|
|
- [ ] 切换语言时所有文本正确更新
|
|
- [ ] 不包含中英文以外的语言
|
|
|
|
### 9.3 Code Quality Acceptance
|
|
|
|
- [ ] 所有单元测试通过 (`npm run test`)
|
|
- [ ] TypeScript 类型检查通过 (`npm run type-check`)
|
|
- [ ] ESLint 检查通过 (`npm run lint`)
|
|
- [ ] 代码风格与现有适配器一致
|
|
- [ ] 关键逻辑有中文注释
|
|
|
|
### 9.4 User Experience Acceptance
|
|
|
|
- [ ] 引擎选择器包含所有 17 个引擎
|
|
- [ ] 引擎切换流畅无卡顿
|
|
- [ ] 搜索结果在正确的引擎中打开
|
|
- [ ] 警告消息清晰易懂
|
|
- [ ] 不支持的语法不阻止搜索
|
|
|
|
---
|
|
|
|
## 10. Post-Implementation
|
|
|
|
### 10.1 Documentation Updates
|
|
|
|
- [ ] 更新 `README.md` 支持的引擎列表
|
|
- [ ] 生成 `CHANGELOG.md` 版本 1.9.0
|
|
- [ ] 更新 `manifest.json` 版本号
|
|
|
|
### 10.2 Version Release
|
|
|
|
**新版本号**: 1.9.0
|
|
|
|
**发布说明**:
|
|
```markdown
|
|
## v1.9.0 - 2025-11-26
|
|
|
|
### 新增功能
|
|
- ✨ 新增 7 个搜索引擎支持
|
|
- Yahoo Search (雅虎搜索)
|
|
- Startpage (隐私友好搜索)
|
|
- Ecosia (环保搜索引擎)
|
|
- Qwant (欧洲隐私搜索)
|
|
- Naver (韩国搜索引擎)
|
|
- Sogou (搜狗搜索)
|
|
- 360搜索
|
|
|
|
### 改进
|
|
- 🌐 完整的中英文国际化支持
|
|
- ⚙️ 智能语法降级处理
|
|
- ✅ 完整的单元测试覆盖
|
|
|
|
### 技术细节
|
|
- 所有语法支持与官方搜索引擎保持一致
|
|
- 多关键词 OR 组合在支持的引擎中正常工作
|
|
- 不支持的语法自动降级并发出友好警告
|
|
```
|
|
|
|
### 10.3 Future Enhancements
|
|
|
|
**可选后续改进** (不在本次实施范围):
|
|
1. 添加 Perplexity AI 和 You.com 支持
|
|
2. 实现 Qwant Qwick 快捷方式
|
|
3. 优化中文搜索引擎的分词处理
|
|
4. 添加引擎特定的搜索建议
|
|
|
|
---
|
|
|
|
## 11. Reference Materials
|
|
|
|
### 11.1 Official Documentation
|
|
|
|
- **Yahoo**: Bing 语法兼容性参考
|
|
- **Startpage**: [官方 Help Center](https://support.startpage.com/)
|
|
- **Ecosia**: [官方 FAQ](https://ecosia.zendesk.com/)
|
|
- **Qwant**: [官方帮助页面](https://help.qwant.com/)
|
|
- **Naver**: [Naver Search API 文档](https://developers.naver.com/)
|
|
- **Sogou**: [搜狗帮助中心](https://help.sogou.com/)
|
|
- **360搜索**: [360 帮助页面](https://help.so.com/)
|
|
|
|
### 11.2 Existing Code References
|
|
|
|
- **Google Adapter**: `/src/services/adapters/google.ts` (最完整语法支持)
|
|
- **Bing Adapter**: `/src/services/adapters/bing.ts` (Yahoo 参考)
|
|
- **Baidu Adapter**: `/src/services/adapters/baidu.ts` (中文引擎参考)
|
|
- **Factory Pattern**: `/src/services/adapters/factory.ts`
|
|
- **Type Definitions**: `/src/types/index.ts`
|
|
- **i18n System**: `/src/i18n/translations.ts`
|
|
|
|
### 11.3 Test Examples
|
|
|
|
- **Twitter Tests**: `/src/services/adapters/twitter.test.ts` (现有测试参考)
|
|
|
|
---
|
|
|
|
## Appendix A: Complete Syntax Support Matrix
|
|
|
|
| 搜索引擎 | site | filetype | exact | intitle | inurl | intext | OR | exclude | date_range | cache | related |
|
|
|---------|------|----------|-------|---------|-------|--------|----|---------|-----------|----- --|---------|
|
|
| Yahoo | ✅ | ✅ | ✅ | ✅ | ✅ | ✅* | ✅ | ✅ | ✅ | ❌ | ✅ |
|
|
| Startpage | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
|
| Ecosia | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
|
| Qwant | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
|
|
| Naver | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
| Sogou | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
|
| 360搜索 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
|
|
|
|
**注释**:
|
|
- `*` Yahoo 使用 `inbody:` 而不是 `intext:`
|
|
- ✅ = 完整支持
|
|
- ❌ = 不支持,需要降级处理
|
|
|
|
---
|
|
|
|
## Appendix B: Multi-Keyword OR Support Matrix
|
|
|
|
| 搜索引擎 | sites OR | fileTypes OR | exactMatches 并列 |
|
|
|---------|----------|--------------|------------------|
|
|
| Yahoo | ✅ | ✅ | ✅ |
|
|
| Startpage | ✅ | ✅ | ✅ |
|
|
| Ecosia | ✅ | ✅ | ✅ |
|
|
| Qwant | ❌ | ❌ | ✅ |
|
|
| Naver | ❌ | ❌ | ✅ |
|
|
| Sogou | ✅ | ✅ | ✅ |
|
|
| 360搜索 | ❌ | ❌ | ✅ |
|
|
|
|
---
|
|
|
|
**Document Status**: ✅ Ready for Implementation
|
|
**Estimated Implementation Time**: 7-10 hours
|
|
**Last Updated**: 2025-11-26
|