chore: sync local changes to Gitea

This commit is contained in:
shumengya
2026-06-24 22:10:22 +08:00
parent df308e3ae6
commit 9e24d85cd0
13 changed files with 81 additions and 331 deletions

View File

@@ -10,7 +10,7 @@ English | [中文](./README.zh.md)
## Features ## Features
- 🔍 **Multi-engine Aggregation** - Use multiple search engines at the same time (Google, Brave, DuckDuckGo, Bing) - 🔍 **Multi-engine Aggregation** - Use DuckDuckGo and Bing at the same time
- 🤖 **AI Enhanced (MCP)** - Native support for Model Context Protocol, one-click search tool integration for **OpenClaw** / **Claude Code** / **Codex** - 🤖 **AI Enhanced (MCP)** - Native support for Model Context Protocol, one-click search tool integration for **OpenClaw** / **Claude Code** / **Codex**
-**Parallel Search** - All search engines are requested concurrently for faster results -**Parallel Search** - All search engines are requested concurrently for faster results
- 🛡️ **Fault Tolerance** - Failure of a single engine does not affect others; unresponsive engines are automatically marked - 🛡️ **Fault Tolerance** - Failure of a single engine does not affect others; unresponsive engines are automatically marked
@@ -132,7 +132,7 @@ Search using query parameters:
curl "https://$YOUR-DOMAIN/search?q=cloudflare" curl "https://$YOUR-DOMAIN/search?q=cloudflare"
# Specify search engines # Specify search engines
curl "https://$YOUR-DOMAIN/search?q=cloudflare&engines=google,brave" curl "https://$YOUR-DOMAIN/search?q=cloudflare&engines=duckduckgo,bing"
# Use token authentication (if TOKEN env var is configured) # Use token authentication (if TOKEN env var is configured)
curl "https://$YOUR-DOMAIN/search?q=cloudflare&token=$YOUR-TOKEN" curl "https://$YOUR-DOMAIN/search?q=cloudflare&token=$YOUR-TOKEN"
@@ -145,7 +145,7 @@ Submit search by POST form:
```bash ```bash
curl -X POST "https://$YOUR-DOMAIN/search" \ curl -X POST "https://$YOUR-DOMAIN/search" \
-d "q=cloudflare" \ -d "q=cloudflare" \
-d "engines=google,brave" -d "engines=duckduckgo,bing"
-d "token=$YOUR-TOKEN" # if TOKEN env var is configured -d "token=$YOUR-TOKEN" # if TOKEN env var is configured
``` ```
@@ -160,13 +160,11 @@ Used to execute search queries and return aggregated results.
| Parameter | Type | Required | Description | Example | | Parameter | Type | Required | Description | Example |
| ------------- | -------- | -------- | ---------------------------------------------------------- | ---------------- | | ------------- | -------- | -------- | ---------------------------------------------------------- | ---------------- |
| `q` / `query` | `string` | yes | Search keyword | `cloudflare` | | `q` / `query` | `string` | yes | Search keyword | `cloudflare` |
| `engines` | `string` | no | Specify search engines, separated by commas | `google,brave` | | `engines` | `string` | no | Specify search engines, separated by commas | `duckduckgo,bing` |
| `token` | `string` | no/yes | Access token (required when `TOKEN` env var is configured) | `$YOUR-TOKEN` | | `token` | `string` | no/yes | Access token (required when `TOKEN` env var is configured) | `$YOUR-TOKEN` |
**Supported Search Engines**: **Supported Search Engines**:
- `google` - Google Search (requires API Key configuration)
- `brave` - Brave Search
- `duckduckgo` - DuckDuckGo Search - `duckduckgo` - DuckDuckGo Search
- `bing` - Bing Search - `bing` - Bing Search
@@ -178,6 +176,8 @@ Used to execute search queries and return aggregated results.
number_of_results: number; // Total number of results number_of_results: number; // Total number of results
enabled_engines: string[]; // Enabled search engine list enabled_engines: string[]; // Enabled search engine list
unresponsive_engines: string[]; // Unresponsive search engine list unresponsive_engines: string[]; // Unresponsive search engine list
empty_engines: string[]; // Engines that responded with no results
engine_errors: Record<string, string>; // Error message by engine
results: Array<{ results: Array<{
title: string; // Result title title: string; // Result title
description: string; // Result description description: string; // Result description
@@ -191,12 +191,12 @@ Used to execute search queries and return aggregated results.
```bash ```bash
# GET request # GET request
curl "https://$YOUR-DOMAIN/search?q=cloudflare&engines=google,brave" curl "https://$YOUR-DOMAIN/search?q=cloudflare&engines=duckduckgo,bing"
# POST request # POST request
curl -X POST "https://$YOUR-DOMAIN/search" \ curl -X POST "https://$YOUR-DOMAIN/search" \
-H "Content-Type: application/x-www-form-urlencoded" \ -H "Content-Type: application/x-www-form-urlencoded" \
-d "q=cloudflare&engines=google,brave" -d "q=cloudflare&engines=duckduckgo,bing"
``` ```
#### Response Example #### Response Example
@@ -205,20 +205,22 @@ curl -X POST "https://$YOUR-DOMAIN/search" \
{ {
"query": "cloudflare", "query": "cloudflare",
"number_of_results": 15, "number_of_results": 15,
"enabled_engines": ["google", "brave", "duckduckgo"], "enabled_engines": ["duckduckgo", "bing"],
"unresponsive_engines": [], "unresponsive_engines": [],
"empty_engines": [],
"engine_errors": {},
"results": [ "results": [
{ {
"title": "Cloudflare - The Web Performance & Security Company", "title": "Cloudflare - The Web Performance & Security Company",
"description": "Cloudflare is on a mission to help build a better Internet...", "description": "Cloudflare is on a mission to help build a better Internet...",
"url": "https://www.cloudflare.com/", "url": "https://www.cloudflare.com/",
"engine": "google" "engine": "duckduckgo"
}, },
{ {
"title": "Cloudflare Workers", "title": "Cloudflare Workers",
"description": "Deploy serverless code instantly across the globe...", "description": "Deploy serverless code instantly across the globe...",
"url": "https://workers.cloudflare.com/", "url": "https://workers.cloudflare.com/",
"engine": "brave" "engine": "bing"
} }
] ]
} }
@@ -230,9 +232,7 @@ curl -X POST "https://$YOUR-DOMAIN/search" \
| Engine | Description | Configuration Required | Enabled by Default | | Engine | Description | Configuration Required | Enabled by Default |
| -------------- | ---------------------------- | ------------------------------- | ------------------ | | -------------- | ---------------------------- | ------------------------------- | ------------------ |
| **Google** | Google Custom Search API | Requires `GOOGLE_API_KEY` and `GOOGLE_CX` | yes | | **DuckDuckGo** | DuckDuckGo HTML search | - | yes |
| **Brave** | Brave Search API | - | yes |
| **DuckDuckGo** | DuckDuckGo Instant Answer API | - | yes |
| **Bing** | Bing Search | - | no (unstable results) | | **Bing** | Bing Search | - | no (unstable results) |
### Basic Working Approach ### Basic Working Approach
@@ -249,13 +249,10 @@ curl -X POST "https://$YOUR-DOMAIN/search" \
| Variable Name | Type | Default | Description | | Variable Name | Type | Default | Description |
| ------------------ | -------- | -------- | --------------------------------------------------- | | ------------------ | -------- | -------- | --------------------------------------------------- |
| `DEFAULT_TIMEOUT` | `string` | `"3000"` | Timeout per search engine request (milliseconds) | | `DEFAULT_TIMEOUT` | `string` | `"3000"` | Timeout per search engine request (milliseconds) |
| `GOOGLE_API_KEY` | `string` | `null` | https://console.cloud.google.com/apis/credentials |
| `GOOGLE_CX` | `string` | `null` | https://programmablesearchengine.google.com/ |
| `TOKEN` | `string` | `null` | Access token. Enables auth when configured to prevent abuse | | `TOKEN` | `string` | `null` | Access token. Enables auth when configured to prevent abuse |
**Notes**: **Notes**:
- Google Custom Search API free tier is limited to 100 requests per day
- After `TOKEN` is configured, all requests must provide a valid token - After `TOKEN` is configured, all requests must provide a valid token
### Configuration Methods ### Configuration Methods
@@ -266,8 +263,6 @@ Edit the `[vars]` section in `wrangler.toml`:
```toml ```toml
[vars] [vars]
GOOGLE_API_KEY = "your-google-api-key"
GOOGLE_CX = "your-google-custom-search-cx"
DEFAULT_TIMEOUT = "3000" DEFAULT_TIMEOUT = "3000"
TOKEN = "your-secret-token-here" TOKEN = "your-secret-token-here"
``` ```
@@ -286,7 +281,7 @@ Build your own aggregated search API and combine results from multiple search en
```javascript ```javascript
const response = await fetch( const response = await fetch(
"https://$YOUR-DOMAIN/search?q=javascript&engines=google,brave", "https://$YOUR-DOMAIN/search?q=javascript&engines=duckduckgo,bing",
); );
const data = await response.json(); const data = await response.json();
console.log(`Found ${data.number_of_results} results`); console.log(`Found ${data.number_of_results} results`);
@@ -311,7 +306,7 @@ async function search(query) {
Collect results from multiple search engines for comparative analysis: Collect results from multiple search engines for comparative analysis:
```javascript ```javascript
const engines = ["google", "brave", "duckduckgo"]; const engines = ["duckduckgo", "bing"];
const results = await fetch( const results = await fetch(
`https://$YOUR-DOMAIN/search?q=AI&engines=${engines.join(",")}`, `https://$YOUR-DOMAIN/search?q=AI&engines=${engines.join(",")}`,
); );
@@ -339,7 +334,6 @@ With MCP (Model Context Protocol), AI assistants can directly call your search s
- In Worker settings, click **Triggers** > **Add Custom Domain** to add a custom domain - In Worker settings, click **Triggers** > **Add Custom Domain** to add a custom domain
2. **Search Engine Limits** 2. **Search Engine Limits**
- Google API free tier is limited to 100 requests per day
- Other search engines generally do not have strict limits, but please use responsibly - Other search engines generally do not have strict limits, but please use responsibly
- Frequent requests may cause temporary rate limiting - Frequent requests may cause temporary rate limiting
@@ -380,7 +374,6 @@ A: Possible reasons:
- Search engine API is temporarily unavailable or timed out - Search engine API is temporarily unavailable or timed out
- No relevant results for the search keyword - No relevant results for the search keyword
- Search engine has rate-limited access - Search engine has rate-limited access
- Google requires API Key configuration before use
You can check the `unresponsive_engines` field in the response to see which engines did not respond. You can check the `unresponsive_engines` field in the response to see which engines did not respond.
@@ -427,7 +420,6 @@ Issues and Pull Requests are welcome!
- [Project GitHub](https://github.com/Yrobot/cloudflare-search) - [Project GitHub](https://github.com/Yrobot/cloudflare-search)
- [Cloudflare Workers Docs](https://developers.cloudflare.com/workers/) - [Cloudflare Workers Docs](https://developers.cloudflare.com/workers/)
- [Google Custom Search API](https://developers.google.com/custom-search/v1/overview)
## Support the Project ## Support the Project

View File

@@ -10,7 +10,7 @@
## 特性 ## 特性
- 🔍 **多引擎聚合** - 同时使用多个搜索引擎Google、Brave、DuckDuckGoBing - 🔍 **多引擎聚合** - 同时使用 DuckDuckGoBing 搜索引擎
- 🤖 **AI 增强 (MCP)** - 原生支持 Model Context Protocol一键为 **OpenClaw** / **Claude Code** / **Codex** 添加搜索工具 - 🤖 **AI 增强 (MCP)** - 原生支持 Model Context Protocol一键为 **OpenClaw** / **Claude Code** / **Codex** 添加搜索工具
-**并行搜索** - 所有搜索引擎同时请求,快速返回结果 -**并行搜索** - 所有搜索引擎同时请求,快速返回结果
- 🛡️ **容错机制** - 单个引擎失败不影响其他引擎,自动标记无响应引擎 - 🛡️ **容错机制** - 单个引擎失败不影响其他引擎,自动标记无响应引擎
@@ -134,7 +134,7 @@ https://$YOUR-DOMAIN/
curl "https://$YOUR-DOMAIN/search?q=cloudflare" curl "https://$YOUR-DOMAIN/search?q=cloudflare"
# 指定搜索引擎 # 指定搜索引擎
curl "https://$YOUR-DOMAIN/search?q=cloudflare&engines=google,brave" curl "https://$YOUR-DOMAIN/search?q=cloudflare&engines=duckduckgo,bing"
# 使用 token 鉴权(如果配置了 TOKEN 环境变量) # 使用 token 鉴权(如果配置了 TOKEN 环境变量)
curl "https://$YOUR-DOMAIN/search?q=cloudflare&token=$YOUR-TOKEN" curl "https://$YOUR-DOMAIN/search?q=cloudflare&token=$YOUR-TOKEN"
@@ -147,7 +147,7 @@ curl "https://$YOUR-DOMAIN/search?q=cloudflare&token=$YOUR-TOKEN"
```bash ```bash
curl -X POST "https://$YOUR-DOMAIN/search" \ curl -X POST "https://$YOUR-DOMAIN/search" \
-d "q=cloudflare" \ -d "q=cloudflare" \
-d "engines=google,brave" -d "engines=duckduckgo,bing"
-d "token=$YOUR-TOKEN" # 如果配置了 TOKEN 环境变量 -d "token=$YOUR-TOKEN" # 如果配置了 TOKEN 环境变量
``` ```
@@ -162,13 +162,11 @@ curl -X POST "https://$YOUR-DOMAIN/search" \
| 参数 | 类型 | 必填 | 说明 | 示例 | | 参数 | 类型 | 必填 | 说明 | 示例 |
| ------------- | -------- | ------ | ----------------------------------------- | -------------- | | ------------- | -------- | ------ | ----------------------------------------- | -------------- |
| `q` / `query` | `string` | yes | 搜索关键词 | `cloudflare` | | `q` / `query` | `string` | yes | 搜索关键词 | `cloudflare` |
| `engines` | `string` | no | 指定搜索引擎,多个用逗号分隔 | `google,brave` | | `engines` | `string` | no | 指定搜索引擎,多个用逗号分隔 | `duckduckgo,bing` |
| `token` | `string` | no/yse | 访问令牌(当配置了 TOKEN 环境变量时必填) | `$YOUR-TOKEN` | | `token` | `string` | no/yes | 访问令牌(当配置了 TOKEN 环境变量时必填) | `$YOUR-TOKEN` |
**支持的搜索引擎** **支持的搜索引擎**
- `google` - Google 搜索(需要配置 API Key
- `brave` - Brave 搜索
- `duckduckgo` - DuckDuckGo 搜索 - `duckduckgo` - DuckDuckGo 搜索
- `bing` - Bing 搜索 - `bing` - Bing 搜索
@@ -180,6 +178,8 @@ curl -X POST "https://$YOUR-DOMAIN/search" \
number_of_results: number; // 结果总数 number_of_results: number; // 结果总数
enabled_engines: string[]; // 启用的搜索引擎列表 enabled_engines: string[]; // 启用的搜索引擎列表
unresponsive_engines: string[]; // 无响应的搜索引擎列表 unresponsive_engines: string[]; // 无响应的搜索引擎列表
empty_engines: string[]; // 响应成功但无结果的搜索引擎列表
engine_errors: Record<string, string>; // 各搜索引擎错误信息
results: Array<{ results: Array<{
title: string; // 结果标题 title: string; // 结果标题
description: string; // 结果描述 description: string; // 结果描述
@@ -193,12 +193,12 @@ curl -X POST "https://$YOUR-DOMAIN/search" \
```bash ```bash
# GET 请求 # GET 请求
curl "https://$YOUR-DOMAIN/search?q=cloudflare&engines=google,brave" curl "https://$YOUR-DOMAIN/search?q=cloudflare&engines=duckduckgo,bing"
# POST 请求 # POST 请求
curl -X POST "https://$YOUR-DOMAIN/search" \ curl -X POST "https://$YOUR-DOMAIN/search" \
-H "Content-Type: application/x-www-form-urlencoded" \ -H "Content-Type: application/x-www-form-urlencoded" \
-d "q=cloudflare&engines=google,brave" -d "q=cloudflare&engines=duckduckgo,bing"
``` ```
#### 响应示例 #### 响应示例
@@ -207,20 +207,22 @@ curl -X POST "https://$YOUR-DOMAIN/search" \
{ {
"query": "cloudflare", "query": "cloudflare",
"number_of_results": 15, "number_of_results": 15,
"enabled_engines": ["google", "brave", "duckduckgo"], "enabled_engines": ["duckduckgo", "bing"],
"unresponsive_engines": [], "unresponsive_engines": [],
"empty_engines": [],
"engine_errors": {},
"results": [ "results": [
{ {
"title": "Cloudflare - The Web Performance & Security Company", "title": "Cloudflare - The Web Performance & Security Company",
"description": "Cloudflare is on a mission to help build a better Internet...", "description": "Cloudflare is on a mission to help build a better Internet...",
"url": "https://www.cloudflare.com/", "url": "https://www.cloudflare.com/",
"engine": "google" "engine": "duckduckgo"
}, },
{ {
"title": "Cloudflare Workers", "title": "Cloudflare Workers",
"description": "Deploy serverless code instantly across the globe...", "description": "Deploy serverless code instantly across the globe...",
"url": "https://workers.cloudflare.com/", "url": "https://workers.cloudflare.com/",
"engine": "brave" "engine": "bing"
} }
] ]
} }
@@ -232,9 +234,7 @@ curl -X POST "https://$YOUR-DOMAIN/search" \
| 引擎 | 说明 | 是否需要配置 | 默认启用 | | 引擎 | 说明 | 是否需要配置 | 默认启用 |
| -------------- | ------------------------ | ----------------------------- | --------------- | | -------------- | ------------------------ | ----------------------------- | --------------- |
| **Google** | Google Custom Search API | 需要 GOOGLE_API_KEY GOOGLE_CX | yes | | **DuckDuckGo** | DuckDuckGo HTML 搜索 | - | yes |
| **Brave** | Brave Search API | - | yes |
| **DuckDuckGo** | DuckDuckGo 即时答案 API | - | yes |
| **Bing** | Bing 搜索 | - | no (结果不稳定) | | **Bing** | Bing 搜索 | - | no (结果不稳定) |
### 基本工作方案 ### 基本工作方案
@@ -251,13 +251,10 @@ curl -X POST "https://$YOUR-DOMAIN/search" \
| 变量名 | 类型 | 默认值 | 说明 | | 变量名 | 类型 | 默认值 | 说明 |
| ----------------- | -------- | -------- | ------------------------------------------------- | | ----------------- | -------- | -------- | ------------------------------------------------- |
| `DEFAULT_TIMEOUT` | `string` | `"3000"` | 单个搜索引擎的超时时间(毫秒) | | `DEFAULT_TIMEOUT` | `string` | `"3000"` | 单个搜索引擎的超时时间(毫秒) |
| `GOOGLE_API_KEY` | `string` | `null` | https://console.cloud.google.com/apis/credentials |
| `GOOGLE_CX` | `string` | `null` | https://programmablesearchengine.google.com/ |
| `TOKEN` | `string` | `null` | 访问令牌,配置后启用鉴权,保护服务不被滥用 | | `TOKEN` | `string` | `null` | 访问令牌,配置后启用鉴权,保护服务不被滥用 |
**注意** **注意**
- Google Custom Search API 免费版每天限制 100 次请求
- `TOKEN` 配置后,所有请求都需要提供有效的 token - `TOKEN` 配置后,所有请求都需要提供有效的 token
### 配置方式 ### 配置方式
@@ -268,8 +265,6 @@ curl -X POST "https://$YOUR-DOMAIN/search" \
```toml ```toml
[vars] [vars]
GOOGLE_API_KEY = "your-google-api-key"
GOOGLE_CX = "your-google-custom-search-cx"
DEFAULT_TIMEOUT = "3000" DEFAULT_TIMEOUT = "3000"
TOKEN = "your-secret-token-here" TOKEN = "your-secret-token-here"
``` ```
@@ -288,7 +283,7 @@ TOKEN = "your-secret-token-here"
```javascript ```javascript
const response = await fetch( const response = await fetch(
"https://$YOUR-DOMAIN/search?q=javascript&engines=google,brave", "https://$YOUR-DOMAIN/search?q=javascript&engines=duckduckgo,bing",
); );
const data = await response.json(); const data = await response.json();
console.log(`找到 ${data.number_of_results} 个结果`); console.log(`找到 ${data.number_of_results} 个结果`);
@@ -313,7 +308,7 @@ async function search(query) {
收集多个搜索引擎的结果进行对比分析: 收集多个搜索引擎的结果进行对比分析:
```javascript ```javascript
const engines = ["google", "brave", "duckduckgo"]; const engines = ["duckduckgo", "bing"];
const results = await fetch( const results = await fetch(
`https://$YOUR-DOMAIN/search?q=AI&engines=${engines.join(",")}`, `https://$YOUR-DOMAIN/search?q=AI&engines=${engines.join(",")}`,
); );
@@ -341,7 +336,6 @@ const byEngine = data.results.reduce((acc, result) => {
- 在 Worker 设置中点击 **Triggers** > **Add Custom Domain** 添加自定义域名 - 在 Worker 设置中点击 **Triggers** > **Add Custom Domain** 添加自定义域名
2. **搜索引擎限制** 2. **搜索引擎限制**
- Google API 免费版每天限制 100 次请求
- 其他搜索引擎一般没有严格限制,但请合理使用 - 其他搜索引擎一般没有严格限制,但请合理使用
- 频繁请求可能导致被临时限制访问 - 频繁请求可能导致被临时限制访问
@@ -382,7 +376,6 @@ A: 可能原因:
- 搜索引擎 API 临时不可用或响应超时 - 搜索引擎 API 临时不可用或响应超时
- 搜索关键词没有相关结果 - 搜索关键词没有相关结果
- 搜索引擎限制了访问频率 - 搜索引擎限制了访问频率
- Google 需要配置 API Key 才能使用
可以查看返回的 `unresponsive_engines` 字段了解哪些引擎没有响应。 可以查看返回的 `unresponsive_engines` 字段了解哪些引擎没有响应。
@@ -429,7 +422,6 @@ A: 建议配置 `TOKEN` 环境变量启用鉴权:
- [项目 GitHub](https://github.com/Yrobot/cloudflare-search) - [项目 GitHub](https://github.com/Yrobot/cloudflare-search)
- [Cloudflare Workers 文档](https://developers.cloudflare.com/workers/) - [Cloudflare Workers 文档](https://developers.cloudflare.com/workers/)
- [Google Custom Search API](https://developers.google.com/custom-search/v1/overview)
## 支持一下 ## 支持一下

View File

@@ -3,20 +3,14 @@ export const env = {
DEFAULT_TIMEOUT: "3000", DEFAULT_TIMEOUT: "3000",
// 支持的搜索引擎列表 - Supported search engines // 支持的搜索引擎列表 - Supported search engines
SUPPORTED_ENGINES: ["google", "brave", "duckduckgo", "bing"], SUPPORTED_ENGINES: ["duckduckgo", "bing"],
// 默认启用的搜索引擎 - Default enabled engines // 默认启用的搜索引擎 - Default enabled engines
DEFAULT_ENGINES: [ DEFAULT_ENGINES: [
"google",
"brave",
"duckduckgo", "duckduckgo",
// "bing" // Bing 目前结果不稳定,默认禁用 - Bing results are currently unstable, disabled by default // "bing" // Bing 目前结果不稳定,默认禁用 - Bing results are currently unstable, disabled by default
], ],
// Google Custom Search API credentials
GOOGLE_API_KEY: null,
GOOGLE_CX: null,
// API 访问令牌 - API access token for authentication // API 访问令牌 - API access token for authentication
// 如果设置了 TOKEN则所有 /search 请求都需要在 header 或 query 中提供此 token // 如果设置了 TOKEN则所有 /search 请求都需要在 header 或 query 中提供此 token
// If TOKEN is set, all /search requests must provide this token in header or query // If TOKEN is set, all /search requests must provide this token in header or query

4
index.d.ts vendored
View File

@@ -22,6 +22,8 @@ export type searchAll = (params: {
number_of_results: number; number_of_results: number;
enabled_engines: string[]; enabled_engines: string[];
unresponsive_engines: string[]; unresponsive_engines: string[];
empty_engines: string[];
engine_errors: Record<string, string>;
results: Array<ResultItem & { engine: string }>; results: Array<ResultItem & { engine: string }>;
}>; }>;
@@ -29,7 +31,5 @@ export interface Env {
DEFAULT_TIMEOUT?: string; DEFAULT_TIMEOUT?: string;
SUPPORTED_ENGINES?: string[]; SUPPORTED_ENGINES?: string[];
DEFAULT_ENGINES?: string[]; DEFAULT_ENGINES?: string[];
GOOGLE_API_KEY?: string;
GOOGLE_CX?: string;
TOKEN?: string; TOKEN?: string;
} }

View File

@@ -86,11 +86,11 @@ const SEARCH_INPUT_SCHEMA = {
type: "array", type: "array",
items: { items: {
type: "string", type: "string",
enum: ["google", "brave", "duckduckgo", "bing"], enum: ["duckduckgo", "bing"],
}, },
description: description:
"Optional: Array of search engines to use. If not specified, uses default engines. " + "Optional: Array of search engines to use. If not specified, uses default engines. " +
"Available engines: google, brave, duckduckgo, bing", "Available engines: duckduckgo, bing",
}, },
}, },
required: ["query"], required: ["query"],
@@ -106,7 +106,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
name: "web_search", name: "web_search",
description: description:
"Search the web for current information, news, or any topic. " + "Search the web for current information, news, or any topic. " +
"Uses multiple engines (Brave, DuckDuckGo, Google, Bing) simultaneously " + "Uses DuckDuckGo and Bing simultaneously " +
"and returns aggregated results with source URLs. " + "and returns aggregated results with source URLs. " +
"Use this when you need real-time information not in your training data. ", "Use this when you need real-time information not in your training data. ",
inputSchema: SEARCH_INPUT_SCHEMA, inputSchema: SEARCH_INPUT_SCHEMA,
@@ -114,7 +114,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
{ {
name: "search", name: "search",
description: description:
"Search across multiple search engines (Google, Brave, DuckDuckGo, Bing) and return aggregated results. " + "Search across DuckDuckGo and Bing and return aggregated results. " +
"This tool provides comprehensive search results from multiple sources, with source attribution for each result.", "This tool provides comprehensive search results from multiple sources, with source attribution for each result.",
inputSchema: SEARCH_INPUT_SCHEMA, inputSchema: SEARCH_INPUT_SCHEMA,
}, },

View File

@@ -25,8 +25,6 @@
"free-search", "free-search",
"privacy", "privacy",
"aggregated-search", "aggregated-search",
"google",
"brave",
"duckduckgo", "duckduckgo",
"bing" "bing"
], ],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -1,7 +1,5 @@
// HTTPS_PROXY=http://localhost:7777 bun ./output/cloudflare-search/utils/dev.js // HTTPS_PROXY=http://localhost:7777 bun ./output/cloudflare-search/utils/dev.js
import searchBrave from "./searchBrave.js";
import searchGoogle from "./searchGoogle.js";
import searchDuckDuckGo from "./searchDuckDuckGo.js"; import searchDuckDuckGo from "./searchDuckDuckGo.js";
import searchBing from "./searchBing.js"; import searchBing from "./searchBing.js";

View File

@@ -5,7 +5,6 @@ import { env } from "../envs.js";
// ============================================ // ============================================
export function getSearchHtml() { export function getSearchHtml() {
const GOOGLE_ENABLED = env.GOOGLE_API_KEY && env.GOOGLE_CX;
const TOKEN_ENABLED = !!env.TOKEN; const TOKEN_ENABLED = !!env.TOKEN;
const DEFAULT_ENGINES = env.DEFAULT_ENGINES || []; const DEFAULT_ENGINES = env.DEFAULT_ENGINES || [];
const handlerEngineDefaultChecked = (engine) => const handlerEngineDefaultChecked = (engine) =>
@@ -94,7 +93,7 @@ export function getSearchHtml() {
</h1> </h1>
<div class="mt-6 text-base text-zinc-600 dark:text-zinc-400"> <div class="mt-6 text-base text-zinc-600 dark:text-zinc-400">
<p class=""> <p class="">
基于 Cloudflare Workers 的多引擎聚合搜索服务。并行搜索 Google、DuckDuckGo、Bing、Brave 四大引擎,快速获取更全面的搜索结果。 基于 Cloudflare Workers 的多引擎聚合搜索服务。并行搜索 DuckDuckGo、Bing 等搜索引擎,快速获取更全面的搜索结果。
</p> </p>
<p class="mt-2"> <p class="mt-2">
如果这个项目对你有帮助,可以 如果这个项目对你有帮助,可以
@@ -112,30 +111,14 @@ export function getSearchHtml() {
<!-- 服务状态 --> <!-- 服务状态 -->
<div class="mt-8 rounded-2xl border ${ <div class="mt-8 rounded-2xl border ${
GOOGLE_ENABLED && !TOKEN_ENABLED TOKEN_ENABLED
? "border-green-200 bg-green-50 dark:border-green-800/40 dark:bg-green-900/10"
: !GOOGLE_ENABLED || TOKEN_ENABLED
? "border-amber-200 bg-amber-50 dark:border-amber-800/40 dark:bg-amber-900/10" ? "border-amber-200 bg-amber-50 dark:border-amber-800/40 dark:bg-amber-900/10"
: "border-zinc-100 dark:border-zinc-700/40" : "border-green-200 bg-green-50 dark:border-green-800/40 dark:bg-green-900/10"
} p-6"> } p-6">
<h2 class="text-sm font-semibold text-zinc-900 dark:text-zinc-100 mb-3"> <h2 class="text-sm font-semibold text-zinc-900 dark:text-zinc-100 mb-3">
⚙️ 服务配置状态 ⚙️ 服务配置状态
</h2> </h2>
<div class="space-y-2 text-sm"> <div class="space-y-2 text-sm">
<div class="flex items-center justify-between">
<span class="text-zinc-700 dark:text-zinc-300">Google 搜索引擎</span>
<span class="${
GOOGLE_ENABLED
? "text-green-600 dark:text-green-400"
: "text-amber-600 dark:text-amber-400"
}">
${
GOOGLE_ENABLED
? "✓ 已配置"
: "○ 未配置 (需要 API Key)"
}
</span>
</div>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="text-zinc-700 dark:text-zinc-300">访问鉴权</span> <span class="text-zinc-700 dark:text-zinc-300">访问鉴权</span>
<span class="${ <span class="${
@@ -147,40 +130,17 @@ export function getSearchHtml() {
</span> </span>
</div> </div>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<span class="text-zinc-700 dark:text-zinc-300">其他引擎 (Brave/DuckDuckGo/Bing)</span> <span class="text-zinc-700 dark:text-zinc-300">搜索引擎 (DuckDuckGo/Bing)</span>
<span class="text-green-600 dark:text-green-400">✓ 可用</span> <span class="text-green-600 dark:text-green-400">✓ 可用</span>
</div> </div>
</div> </div>
${
!GOOGLE_ENABLED
? `
<div class="mt-4 pt-4 border-t border-amber-200 dark:border-amber-800/40">
<p class="text-xs text-amber-700 dark:text-amber-400">
💡 提示: 配置 Google API 后可使用 Google 搜索引擎。在 Cloudflare Dashboard 的 Worker 设置中添加环境变量:<br/>
• <code class="px-1 py-0.5 bg-amber-100 dark:bg-amber-900/30 rounded">GOOGLE_API_KEY</code> - <a href="https://console.cloud.google.com/apis/credentials" target="_blank" class="underline hover:text-amber-900 dark:hover:text-amber-100">获取 API Key</a><br/>
• <code class="px-1 py-0.5 bg-amber-100 dark:bg-amber-900/30 rounded">GOOGLE_CX</code> - <a href="https://programmablesearchengine.google.com/" target="_blank" class="underline hover:text-amber-900 dark:hover:text-amber-100">获取 CX ID</a>
</p>
</div>
`
: ""
}
${ ${
!TOKEN_ENABLED !TOKEN_ENABLED
? ` ? `
<div class="mt-4 pt-4 border-t ${ <div class="mt-4 pt-4 border-t border-green-200 dark:border-green-800/40">
!GOOGLE_ENABLED <p class="text-xs text-green-700 dark:text-green-400">
? "border-amber-200 dark:border-amber-800/40"
: "border-green-200 dark:border-green-800/40"
}">
<p class="text-xs ${
!GOOGLE_ENABLED
? "text-amber-700 dark:text-amber-400"
: "text-green-700 dark:text-green-400"
}">
💡 建议:为防止服务被滥用,建议在 Cloudflare Dashboard 的 Worker 设置中添加环境变量 <code class="px-1 py-0.5 ${ 💡 建议:为防止服务被滥用,建议在 Cloudflare Dashboard 的 Worker 设置中添加环境变量 <code class="px-1 py-0.5 ${
!GOOGLE_ENABLED "bg-green-100 dark:bg-green-900/30"
? "bg-amber-100 dark:bg-amber-900/30"
: "bg-green-100 dark:bg-green-900/30"
} rounded">TOKEN</code> 启用访问鉴权。 } rounded">TOKEN</code> 启用访问鉴权。
</p> </p>
</div> </div>
@@ -213,29 +173,6 @@ export function getSearchHtml() {
选择搜索引擎 (可多选) 选择搜索引擎 (可多选)
</label> </label>
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
<label class="flex items-center space-x-2 ${
GOOGLE_ENABLED
? "cursor-pointer"
: "cursor-not-allowed opacity-50"
}" ${
!GOOGLE_ENABLED ? 'title="Google 引擎需要配置 API Key"' : ""
}>
<input type="checkbox" name="engine" value="google" ${
GOOGLE_ENABLED
? handlerEngineDefaultChecked("google")
: "disabled"
} class="rounded text-blue-500 focus:ring-blue-500 ${
!GOOGLE_ENABLED ? "cursor-not-allowed" : ""
}">
<span class="text-sm text-zinc-700 dark:text-zinc-300">
Google
${
!GOOGLE_ENABLED
? '<span class="text-xs text-zinc-400 dark:text-zinc-500 ml-1">(未配置)</span>'
: ""
}
</span>
</label>
<label class="flex items-center space-x-2 cursor-pointer"> <label class="flex items-center space-x-2 cursor-pointer">
<input type="checkbox" name="engine" value="duckduckgo" ${handlerEngineDefaultChecked( <input type="checkbox" name="engine" value="duckduckgo" ${handlerEngineDefaultChecked(
"duckduckgo" "duckduckgo"
@@ -251,12 +188,6 @@ export function getSearchHtml() {
<span class="text-xs text-zinc-400 dark:text-zinc-500 ml-1">(不稳定)</span> <span class="text-xs text-zinc-400 dark:text-zinc-500 ml-1">(不稳定)</span>
</span> </span>
</label> </label>
<label class="flex items-center space-x-2 cursor-pointer">
<input type="checkbox" name="engine" value="brave" ${handlerEngineDefaultChecked(
"brave"
)} class="rounded text-blue-500 focus:ring-blue-500">
<span class="text-sm text-zinc-700 dark:text-zinc-300">Brave</span>
</label>
</div> </div>
</div> </div>
@@ -343,14 +274,16 @@ export function getSearchHtml() {
<pre class="text-xs overflow-x-auto"><code>{ <pre class="text-xs overflow-x-auto"><code>{
"query": "cloudflare", // 搜索关键词 "query": "cloudflare", // 搜索关键词
"number_of_results": 15, // 结果总数 "number_of_results": 15, // 结果总数
"enabled_engines": ["google", ...], // 启用的搜索引擎列表 "enabled_engines": ["duckduckgo", ...], // 启用的搜索引擎列表
"unresponsive_engines": [], // 无响应的搜索引擎列表 "unresponsive_engines": [], // 无响应的搜索引擎列表
"empty_engines": [], // 响应成功但无结果的搜索引擎列表
"engine_errors": {}, // 各搜索引擎错误信息
"results": [ "results": [
{ {
"title": "...", // 结果标题 "title": "...", // 结果标题
"description": "...", // 结果描述 "description": "...", // 结果描述
"url": "...", // 结果链接 "url": "...", // 结果链接
"engine": "google" // 来源引擎 "engine": "duckduckgo" // 来源引擎
} }
] ]
}</code></pre> }</code></pre>
@@ -365,21 +298,6 @@ export function getSearchHtml() {
🚀 支持的搜索引擎 🚀 支持的搜索引擎
</h2> </h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="rounded-lg bg-zinc-50 p-4 dark:bg-zinc-800/50 ${
!GOOGLE_ENABLED ? "opacity-50" : ""
}">
<div class="flex items-center justify-between mb-2">
<div class="font-medium text-zinc-900 dark:text-zinc-100">Google</div>
${
GOOGLE_ENABLED
? '<span class="text-xs text-green-600 dark:text-green-400">✓ 可用</span>'
: '<span class="text-xs text-amber-600 dark:text-amber-400">○ 需配置</span>'
}
</div>
<p class="text-xs text-zinc-600 dark:text-zinc-400">
全球最大的搜索引擎,需要配置 Google Custom Search API
</p>
</div>
<div class="rounded-lg bg-zinc-50 p-4 dark:bg-zinc-800/50"> <div class="rounded-lg bg-zinc-50 p-4 dark:bg-zinc-800/50">
<div class="flex items-center justify-between mb-2"> <div class="flex items-center justify-between mb-2">
<div class="font-medium text-zinc-900 dark:text-zinc-100">DuckDuckGo</div> <div class="font-medium text-zinc-900 dark:text-zinc-100">DuckDuckGo</div>
@@ -387,13 +305,6 @@ export function getSearchHtml() {
</div> </div>
<p class="text-xs text-zinc-600 dark:text-zinc-400">注重隐私保护的搜索引擎,无需配置</p> <p class="text-xs text-zinc-600 dark:text-zinc-400">注重隐私保护的搜索引擎,无需配置</p>
</div> </div>
<div class="rounded-lg bg-zinc-50 p-4 dark:bg-zinc-800/50">
<div class="flex items-center justify-between mb-2">
<div class="font-medium text-zinc-900 dark:text-zinc-100">Brave Search</div>
<span class="text-xs text-green-600 dark:text-green-400">✓ 可用</span>
</div>
<p class="text-xs text-zinc-600 dark:text-zinc-400">独立的搜索引擎,无需配置</p>
</div>
<div class="rounded-lg bg-zinc-50 p-4 dark:bg-zinc-800/50 opacity-60"> <div class="rounded-lg bg-zinc-50 p-4 dark:bg-zinc-800/50 opacity-60">
<div class="flex items-center justify-between mb-2"> <div class="flex items-center justify-between mb-2">
<div class="font-medium text-zinc-900 dark:text-zinc-100">Bing</div> <div class="font-medium text-zinc-900 dark:text-zinc-100">Bing</div>
@@ -425,11 +336,6 @@ export function getSearchHtml() {
在 Cloudflare Dashboard → Workers & Pages → 您的 Worker → 设置 → 变量 中添加: 在 Cloudflare Dashboard → Workers & Pages → 您的 Worker → 设置 → 变量 中添加:
</p> </p>
<ul class="text-xs text-blue-700 dark:text-blue-300 mt-1 ml-4 list-disc"> <ul class="text-xs text-blue-700 dark:text-blue-300 mt-1 ml-4 list-disc">
${
!GOOGLE_ENABLED
? '<li><code class="px-1 py-0.5 bg-blue-100 dark:bg-blue-900/30 rounded">GOOGLE_API_KEY</code> 和 <code class="px-1 py-0.5 bg-blue-100 dark:bg-blue-900/30 rounded">GOOGLE_CX</code> - 启用 Google 搜索 (<a href="https://console.cloud.google.com/apis/credentials" target="_blank" class="underline hover:text-blue-900 dark:hover:text-blue-100">获取 API Key</a> / <a href="https://programmablesearchengine.google.com/" target="_blank" class="underline hover:text-blue-900 dark:hover:text-blue-100">获取 CX</a>)</li>'
: ""
}
${ ${
!TOKEN_ENABLED !TOKEN_ENABLED
? '<li><code class="px-1 py-0.5 bg-blue-100 dark:bg-blue-900/30 rounded">TOKEN</code> - 启用访问鉴权 (建议)</li>' ? '<li><code class="px-1 py-0.5 bg-blue-100 dark:bg-blue-900/30 rounded">TOKEN</code> - 启用访问鉴权 (建议)</li>'
@@ -613,7 +519,7 @@ export function getSearchHtml() {
const tokenBodyParam = TOKEN_ENABLED && currentToken ? \`&token=\${currentToken}\` : ''; const tokenBodyParam = TOKEN_ENABLED && currentToken ? \`&token=\${currentToken}\` : '';
document.getElementById('apiExample1').textContent = currentOrigin + '/search?q=cloudflare' + tokenParam; document.getElementById('apiExample1').textContent = currentOrigin + '/search?q=cloudflare' + tokenParam;
document.getElementById('apiExample2').textContent = 'curl -X POST "' + currentOrigin + '/search" -d "q=cloudflare&engines=google,brave' + tokenBodyParam + '"'; document.getElementById('apiExample2').textContent = 'curl -X POST "' + currentOrigin + '/search" -d "q=cloudflare&engines=duckduckgo,bing' + tokenBodyParam + '"';
document.getElementById('mcp-config-json').innerHTML = \`{ document.getElementById('mcp-config-json').innerHTML = \`{
"mcpServers": { "mcpServers": {
"cloudflare-search": { "cloudflare-search": {

View File

@@ -1,70 +0,0 @@
import { normalizeResults } from "./index.js";
// type subSearch under index.d.ts
// TODO: support language, time_range, pageno
async function searchBrave({ query, language, time_range, pageno, signal }) {
const searchUrl = `https://search.brave.com/search?q=${encodeURIComponent(
query
)}&spellcheck=0&source=web&summary=0`;
const response = await fetch(searchUrl, {
signal,
headers: {
accept:
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7",
priority: "u=0, i",
"sec-ch-ua":
'"Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"macOS"',
"sec-fetch-dest": "document",
"sec-fetch-mode": "navigate",
"sec-fetch-site": "none",
"sec-fetch-user": "?1",
"upgrade-insecure-requests": "1",
cookie:
"ui_lang=zh; country=all; safesearch=off; useLocation=0; __Secure-sku#brave-search-captcha=eyJ0eXBlIjoic2luZ2xlLXVzZSIsInZlcnNpb24iOjEsInNrdSI6ImJyYXZlLXNlYXJjaC1jYXB0Y2hhIiwicHJlc2VudGF0aW9uIjoiZXlKcGMzTjFaWElpT2lKaWNtRjJaUzVqYjIwL2MydDFQV0p5WVhabExYTmxZWEpqYUMxallYQjBZMmhoSWl3aWMybG5ibUYwZFhKbElqb2lPRFY2YVU1S1JVMXFTbXhvTTI5TE5UUXhTMFJtTlZkdU4yWjJiall2VmtVMVVHTmFkMlIzV2poMGFreDBTR0V2Tm1SVVMyVjRZWFpSZVVKSFNIcHRVR0ZLUWtSbVFUTkxlV3gzUjNaSVkyZ3ZUVXhvTTFFOVBTSXNJblFpT2lKM1dVZzJTVTFVTlRsTGMzRnpaM1ptTWpVMVRDOHpOVlZrYzFVNU1sRnlSSGxGZVdoWVlXSXdOa2x3TVhscU1HMUlUa0ozUjNGVVpYTlliWGh1TlVGREx5OXdLMUpJUjJzemJrTTJVWHBQWW1aUFpVOHlVVDA5SW4wPSJ9",
},
referrer: "https://search.brave.com/search?source=web",
});
if (!response.ok) {
console.error(`Brave search failed: ${response.status}`);
return [];
}
const html = await response.text();
if (!html) throw new Error("html is empty");
let data;
html.split("\n").forEach((line) => {
if (data) return;
if (line.trimStart().startsWith(`data: [{type:"data",`)) {
const pureLine = line.trim();
const jsonStr = pureLine.slice(
"data: ".length,
pureLine.endsWith(",") ? -1 : undefined
);
data = eval("(" + jsonStr + ")");
}
});
if (!data) throw new Error("can't find data in html");
let result;
data.forEach((item) => {
if (result) return;
if (item?.data?.body?.response?.web?.results)
result = normalizeResults(item.data.body.response.web.results);
});
if (!result) throw new Error("can't find result in data");
if (!(result instanceof Array)) throw new Error("result is not an array");
return result;
}
export default searchBrave;

View File

@@ -1,34 +0,0 @@
import { normalizeResults } from "./index.js";
import { env } from "../envs.js";
// type subSearch under index.d.ts
// TODO: support language, time_range, pageno
async function searchGoogle({ query, language, time_range, pageno, signal }) {
const searchUrl = `https://www.googleapis.com/customsearch/v1?key=${env.GOOGLE_API_KEY}&cx=${env.GOOGLE_CX}&q=${encodeURIComponent(
query
)}`;
const response = await fetch(searchUrl, { signal });
if (!response.ok) {
console.error(await response.text());
return [];
}
const data = await response.json();
const results = [];
if (data.items && Array.isArray(data.items)) {
for (const item of data.items) {
results.push({
title: item.title,
url: item.link,
content: item.snippet || "",
});
}
}
return normalizeResults(results);
}
export default searchGoogle;

View File

@@ -1,13 +1,9 @@
import { env, setEnv } from "./envs.js"; import { env, setEnv } from "./envs.js";
import { getSearchHtml } from "./utils/getHTML.js"; import { getSearchHtml } from "./utils/getHTML.js";
import searchGoogle from "./utils/searchGoogle.js";
import searchBrave from "./utils/searchBrave.js";
import searchDuckDuckGo from "./utils/searchDuckDuckGo.js"; import searchDuckDuckGo from "./utils/searchDuckDuckGo.js";
import searchBing from "./utils/searchBing.js"; import searchBing from "./utils/searchBing.js";
const SEARCH_ENGINES = { const SEARCH_ENGINES = {
google: searchGoogle,
brave: searchBrave,
duckduckgo: searchDuckDuckGo, duckduckgo: searchDuckDuckGo,
bing: searchBing, bing: searchBing,
}; };
@@ -23,12 +19,7 @@ function parseEngines(enginesParam) {
return enginesParam return enginesParam
.split(",") .split(",")
.map((e) => e.trim().toLowerCase()) .map((e) => e.trim().toLowerCase())
.filter((e) => { .filter((e) => env.SUPPORTED_ENGINES.includes(e));
// Filter out google if not enabled
if (e === "google" && !(env.GOOGLE_API_KEY && env.GOOGLE_CX))
return false;
return env.SUPPORTED_ENGINES.includes(e);
});
} }
/** /**
@@ -41,28 +32,35 @@ async function searchSingle(engineName, query) {
const searchFn = SEARCH_ENGINES[engineName]; const searchFn = SEARCH_ENGINES[engineName];
if (!searchFn) { if (!searchFn) {
console.warn(`Unknown engine: ${engineName}`); console.warn(`Unknown engine: ${engineName}`);
return []; return { results: [], error: "Unknown engine" };
} }
// 创建 AbortController 用于取消请求 // 创建 AbortController 用于取消请求
const controller = new AbortController(); const controller = new AbortController();
const timeout = parseInt(env.DEFAULT_TIMEOUT ?? "3000", 10); const timeout = parseInt(env.DEFAULT_TIMEOUT ?? "3000", 10);
let timeoutId;
try { try {
// 设置超时自动取消 // 设置超时自动取消
const timeoutId = setTimeout(() => controller.abort(), timeout); timeoutId = setTimeout(() => controller.abort(), timeout);
const result = await searchFn({ query, signal: controller.signal }); const results = await searchFn({ query, signal: controller.signal });
clearTimeout(timeoutId); clearTimeout(timeoutId);
return result; return { results, error: null };
} catch (error) { } catch (error) {
const message =
error.name === "AbortError"
? `Timeout after ${timeout}ms`
: error.message || String(error);
if (error.name === "AbortError") { if (error.name === "AbortError") {
console.error(`[${engineName}] Timeout after ${timeout}ms`); console.error(`[${engineName}] ${message}`);
} else { } else {
console.error(`[${engineName}] Error:`, error.message); console.error(`[${engineName}] Error:`, message);
} }
return []; clearTimeout(timeoutId);
return { results: [], error: message };
} }
} }
@@ -86,21 +84,27 @@ async function searchAll({ query, engines }) {
// Collect resultsArr and track unresponsive engines // Collect resultsArr and track unresponsive engines
const results = []; const results = [];
const unresponsive = []; const unresponsive = [];
const empty = [];
const errors = {};
resultsArr.forEach((result, index) => { resultsArr.forEach((result, index) => {
const engineName = enabledEngines[index]; const engineName = enabledEngines[index];
if (result.status === "fulfilled" && result.value.length > 0) { if (result.status === "fulfilled" && result.value.results.length > 0) {
results.push( results.push(
...result.value.map((item) => ({ ...result.value.results.map((item) => ({
...item, ...item,
engine: engineName, engine: engineName,
})) }))
); );
} else if (result.status === "fulfilled" && result.value.error) {
unresponsive.push(engineName);
errors[engineName] = result.value.error;
} else if (result.status === "fulfilled") {
empty.push(engineName);
} else { } else {
unresponsive.push(engineName); unresponsive.push(engineName);
if (result.status === "rejected") { errors[engineName] = result.reason?.message || String(result.reason);
console.error(`[${engineName}] Rejected:`, result.reason); console.error(`[${engineName}] Rejected:`, result.reason);
}
} }
}); });
@@ -109,6 +113,8 @@ async function searchAll({ query, engines }) {
number_of_results: results.length, number_of_results: results.length,
enabled_engines: enabledEngines, enabled_engines: enabledEngines,
unresponsive_engines: unresponsive, unresponsive_engines: unresponsive,
empty_engines: empty,
engine_errors: errors,
results, results,
}; };
} }

View File

@@ -1,6 +1,3 @@
# Cloudflare Worker 配置文件
# 更多配置选项: https://developers.cloudflare.com/workers/wrangler/configuration/
# Worker 名称(修改为你想要的名称) # Worker 名称(修改为你想要的名称)
name = "cloudflare-search" name = "cloudflare-search"
@@ -15,45 +12,16 @@ compatibility_date = "2024-12-04"
# 设置为 true 会自动分配一个 xxx.workers.dev 域名 # 设置为 true 会自动分配一个 xxx.workers.dev 域名
workers_dev = true workers_dev = true
# 账号 ID (可选,部署时会自动填充)
# account_id = "your-account-id"
# ============================================
# 路由配置(可选)
# ============================================
# 如果你想将 Worker 绑定到自定义域名,取消注释并配置:
# routes = [
# { pattern = "search.yourdomain.com/*", zone_name = "yourdomain.com" }
# ]
# ============================================ # ============================================
# 环境变量(可选) # 环境变量(可选)
# ============================================ # ============================================
[vars] [vars]
# Google Custom Search API 配置
# GOOGLE_API_KEY = ""
# GOOGLE_CX = ""
# API 访问令牌(可选) # API 访问令牌(可选)
# 配置后,所有 /search 请求都需要提供此 token 进行鉴权 # 配置后,所有 /search 请求都需要提供此 token 进行鉴权
# 可以通过 Authorization header (Bearer token) 或 query 参数 (?token=xxx) 传递 # 可以通过 Authorization header (Bearer token) 或 query 参数 (?token=xxx) 传递
# TOKEN = "" TOKEN = "shumengya520"
# ============================================
# 环境配置(可选)
# ============================================
# 生产环境配置
# [env.production]
# name = "cloudflare-search-prod"
# workers_dev = false
# routes = [
# { pattern = "search.yourdomain.com/*", zone_name = "yourdomain.com" }
# ]
# 开发环境配置
# [env.staging]
# name = "cloudflare-search-staging"
# workers_dev = true
# ============================================ # ============================================
# 资源限制(可选) # 资源限制(可选)