diff --git a/README.md b/README.md index 28f69ed..22c7277 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ English | [中文](./README.zh.md) ## 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** - ⚡ **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 @@ -132,7 +132,7 @@ Search using query parameters: curl "https://$YOUR-DOMAIN/search?q=cloudflare" # 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) curl "https://$YOUR-DOMAIN/search?q=cloudflare&token=$YOUR-TOKEN" @@ -145,7 +145,7 @@ Submit search by POST form: ```bash curl -X POST "https://$YOUR-DOMAIN/search" \ -d "q=cloudflare" \ - -d "engines=google,brave" + -d "engines=duckduckgo,bing" -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 | | ------------- | -------- | -------- | ---------------------------------------------------------- | ---------------- | | `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` | **Supported Search Engines**: -- `google` - Google Search (requires API Key configuration) -- `brave` - Brave Search - `duckduckgo` - DuckDuckGo 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 enabled_engines: string[]; // Enabled search engine list unresponsive_engines: string[]; // Unresponsive search engine list + empty_engines: string[]; // Engines that responded with no results + engine_errors: Record; // Error message by engine results: Array<{ title: string; // Result title description: string; // Result description @@ -191,12 +191,12 @@ Used to execute search queries and return aggregated results. ```bash # GET request -curl "https://$YOUR-DOMAIN/search?q=cloudflare&engines=google,brave" +curl "https://$YOUR-DOMAIN/search?q=cloudflare&engines=duckduckgo,bing" # POST request curl -X POST "https://$YOUR-DOMAIN/search" \ -H "Content-Type: application/x-www-form-urlencoded" \ - -d "q=cloudflare&engines=google,brave" + -d "q=cloudflare&engines=duckduckgo,bing" ``` #### Response Example @@ -205,20 +205,22 @@ curl -X POST "https://$YOUR-DOMAIN/search" \ { "query": "cloudflare", "number_of_results": 15, - "enabled_engines": ["google", "brave", "duckduckgo"], + "enabled_engines": ["duckduckgo", "bing"], "unresponsive_engines": [], + "empty_engines": [], + "engine_errors": {}, "results": [ { "title": "Cloudflare - The Web Performance & Security Company", "description": "Cloudflare is on a mission to help build a better Internet...", "url": "https://www.cloudflare.com/", - "engine": "google" + "engine": "duckduckgo" }, { "title": "Cloudflare Workers", "description": "Deploy serverless code instantly across the globe...", "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 | | -------------- | ---------------------------- | ------------------------------- | ------------------ | -| **Google** | Google Custom Search API | Requires `GOOGLE_API_KEY` and `GOOGLE_CX` | yes | -| **Brave** | Brave Search API | - | yes | -| **DuckDuckGo** | DuckDuckGo Instant Answer API | - | yes | +| **DuckDuckGo** | DuckDuckGo HTML search | - | yes | | **Bing** | Bing Search | - | no (unstable results) | ### Basic Working Approach @@ -249,13 +249,10 @@ curl -X POST "https://$YOUR-DOMAIN/search" \ | Variable Name | Type | Default | Description | | ------------------ | -------- | -------- | --------------------------------------------------- | | `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 | **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 ### Configuration Methods @@ -266,8 +263,6 @@ Edit the `[vars]` section in `wrangler.toml`: ```toml [vars] -GOOGLE_API_KEY = "your-google-api-key" -GOOGLE_CX = "your-google-custom-search-cx" DEFAULT_TIMEOUT = "3000" TOKEN = "your-secret-token-here" ``` @@ -286,7 +281,7 @@ Build your own aggregated search API and combine results from multiple search en ```javascript 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(); 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: ```javascript -const engines = ["google", "brave", "duckduckgo"]; +const engines = ["duckduckgo", "bing"]; const results = await fetch( `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 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 - Frequent requests may cause temporary rate limiting @@ -380,7 +374,6 @@ A: Possible reasons: - Search engine API is temporarily unavailable or timed out - No relevant results for the search keyword - 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. @@ -427,7 +420,6 @@ Issues and Pull Requests are welcome! - [Project GitHub](https://github.com/Yrobot/cloudflare-search) - [Cloudflare Workers Docs](https://developers.cloudflare.com/workers/) -- [Google Custom Search API](https://developers.google.com/custom-search/v1/overview) ## Support the Project diff --git a/README.zh.md b/README.zh.md index d1195ea..b172f7c 100644 --- a/README.zh.md +++ b/README.zh.md @@ -10,7 +10,7 @@ ## 特性 -- 🔍 **多引擎聚合** - 同时使用多个搜索引擎(Google、Brave、DuckDuckGo、Bing) +- 🔍 **多引擎聚合** - 同时使用 DuckDuckGo 和 Bing 搜索引擎 - 🤖 **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&engines=google,brave" +curl "https://$YOUR-DOMAIN/search?q=cloudflare&engines=duckduckgo,bing" # 使用 token 鉴权(如果配置了 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 curl -X POST "https://$YOUR-DOMAIN/search" \ -d "q=cloudflare" \ - -d "engines=google,brave" + -d "engines=duckduckgo,bing" -d "token=$YOUR-TOKEN" # 如果配置了 TOKEN 环境变量 ``` @@ -162,13 +162,11 @@ curl -X POST "https://$YOUR-DOMAIN/search" \ | 参数 | 类型 | 必填 | 说明 | 示例 | | ------------- | -------- | ------ | ----------------------------------------- | -------------- | | `q` / `query` | `string` | yes | 搜索关键词 | `cloudflare` | -| `engines` | `string` | no | 指定搜索引擎,多个用逗号分隔 | `google,brave` | -| `token` | `string` | no/yse | 访问令牌(当配置了 TOKEN 环境变量时必填) | `$YOUR-TOKEN` | +| `engines` | `string` | no | 指定搜索引擎,多个用逗号分隔 | `duckduckgo,bing` | +| `token` | `string` | no/yes | 访问令牌(当配置了 TOKEN 环境变量时必填) | `$YOUR-TOKEN` | **支持的搜索引擎**: -- `google` - Google 搜索(需要配置 API Key) -- `brave` - Brave 搜索 - `duckduckgo` - DuckDuckGo 搜索 - `bing` - Bing 搜索 @@ -180,6 +178,8 @@ curl -X POST "https://$YOUR-DOMAIN/search" \ number_of_results: number; // 结果总数 enabled_engines: string[]; // 启用的搜索引擎列表 unresponsive_engines: string[]; // 无响应的搜索引擎列表 + empty_engines: string[]; // 响应成功但无结果的搜索引擎列表 + engine_errors: Record; // 各搜索引擎错误信息 results: Array<{ title: string; // 结果标题 description: string; // 结果描述 @@ -193,12 +193,12 @@ curl -X POST "https://$YOUR-DOMAIN/search" \ ```bash # GET 请求 -curl "https://$YOUR-DOMAIN/search?q=cloudflare&engines=google,brave" +curl "https://$YOUR-DOMAIN/search?q=cloudflare&engines=duckduckgo,bing" # POST 请求 curl -X POST "https://$YOUR-DOMAIN/search" \ -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", "number_of_results": 15, - "enabled_engines": ["google", "brave", "duckduckgo"], + "enabled_engines": ["duckduckgo", "bing"], "unresponsive_engines": [], + "empty_engines": [], + "engine_errors": {}, "results": [ { "title": "Cloudflare - The Web Performance & Security Company", "description": "Cloudflare is on a mission to help build a better Internet...", "url": "https://www.cloudflare.com/", - "engine": "google" + "engine": "duckduckgo" }, { "title": "Cloudflare Workers", "description": "Deploy serverless code instantly across the globe...", "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 | -| **Brave** | Brave Search API | - | yes | -| **DuckDuckGo** | DuckDuckGo 即时答案 API | - | yes | +| **DuckDuckGo** | DuckDuckGo HTML 搜索 | - | yes | | **Bing** | Bing 搜索 | - | no (结果不稳定) | ### 基本工作方案 @@ -251,13 +251,10 @@ curl -X POST "https://$YOUR-DOMAIN/search" \ | 变量名 | 类型 | 默认值 | 说明 | | ----------------- | -------- | -------- | ------------------------------------------------- | | `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` | 访问令牌,配置后启用鉴权,保护服务不被滥用 | **注意**: -- Google Custom Search API 免费版每天限制 100 次请求 - `TOKEN` 配置后,所有请求都需要提供有效的 token ### 配置方式 @@ -268,8 +265,6 @@ curl -X POST "https://$YOUR-DOMAIN/search" \ ```toml [vars] -GOOGLE_API_KEY = "your-google-api-key" -GOOGLE_CX = "your-google-custom-search-cx" DEFAULT_TIMEOUT = "3000" TOKEN = "your-secret-token-here" ``` @@ -288,7 +283,7 @@ TOKEN = "your-secret-token-here" ```javascript 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(); console.log(`找到 ${data.number_of_results} 个结果`); @@ -313,7 +308,7 @@ async function search(query) { 收集多个搜索引擎的结果进行对比分析: ```javascript -const engines = ["google", "brave", "duckduckgo"]; +const engines = ["duckduckgo", "bing"]; const results = await fetch( `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** 添加自定义域名 2. **搜索引擎限制** - - Google API 免费版每天限制 100 次请求 - 其他搜索引擎一般没有严格限制,但请合理使用 - 频繁请求可能导致被临时限制访问 @@ -382,7 +376,6 @@ A: 可能原因: - 搜索引擎 API 临时不可用或响应超时 - 搜索关键词没有相关结果 - 搜索引擎限制了访问频率 -- Google 需要配置 API Key 才能使用 可以查看返回的 `unresponsive_engines` 字段了解哪些引擎没有响应。 @@ -429,7 +422,6 @@ A: 建议配置 `TOKEN` 环境变量启用鉴权: - [项目 GitHub](https://github.com/Yrobot/cloudflare-search) - [Cloudflare Workers 文档](https://developers.cloudflare.com/workers/) -- [Google Custom Search API](https://developers.google.com/custom-search/v1/overview) ## 支持一下 diff --git a/envs.js b/envs.js index 8ab2844..a8fb7a4 100644 --- a/envs.js +++ b/envs.js @@ -3,20 +3,14 @@ export const env = { DEFAULT_TIMEOUT: "3000", // 支持的搜索引擎列表 - Supported search engines - SUPPORTED_ENGINES: ["google", "brave", "duckduckgo", "bing"], + SUPPORTED_ENGINES: ["duckduckgo", "bing"], // 默认启用的搜索引擎 - Default enabled engines DEFAULT_ENGINES: [ - "google", - "brave", "duckduckgo", // "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 // 如果设置了 TOKEN,则所有 /search 请求都需要在 header 或 query 中提供此 token // If TOKEN is set, all /search requests must provide this token in header or query diff --git a/index.d.ts b/index.d.ts index c9b3ebf..0553226 100644 --- a/index.d.ts +++ b/index.d.ts @@ -22,6 +22,8 @@ export type searchAll = (params: { number_of_results: number; enabled_engines: string[]; unresponsive_engines: string[]; + empty_engines: string[]; + engine_errors: Record; results: Array; }>; @@ -29,7 +31,5 @@ export interface Env { DEFAULT_TIMEOUT?: string; SUPPORTED_ENGINES?: string[]; DEFAULT_ENGINES?: string[]; - GOOGLE_API_KEY?: string; - GOOGLE_CX?: string; TOKEN?: string; } diff --git a/mcp/cf-search-mcp.js b/mcp/cf-search-mcp.js index eda0b92..2ac032a 100644 --- a/mcp/cf-search-mcp.js +++ b/mcp/cf-search-mcp.js @@ -86,11 +86,11 @@ const SEARCH_INPUT_SCHEMA = { type: "array", items: { type: "string", - enum: ["google", "brave", "duckduckgo", "bing"], + enum: ["duckduckgo", "bing"], }, description: "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"], @@ -106,7 +106,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { name: "web_search", description: "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. " + "Use this when you need real-time information not in your training data. ", inputSchema: SEARCH_INPUT_SCHEMA, @@ -114,7 +114,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { { name: "search", 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.", inputSchema: SEARCH_INPUT_SCHEMA, }, diff --git a/package.json b/package.json index 3750c18..fd01645 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,6 @@ "free-search", "privacy", "aggregated-search", - "google", - "brave", "duckduckgo", "bing" ], diff --git a/screenshot.png b/screenshot.png deleted file mode 100644 index e8aa08f..0000000 Binary files a/screenshot.png and /dev/null differ diff --git a/utils/dev.js b/utils/dev.js index c541482..c3c3b72 100644 --- a/utils/dev.js +++ b/utils/dev.js @@ -1,7 +1,5 @@ // 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 searchBing from "./searchBing.js"; diff --git a/utils/getHTML.js b/utils/getHTML.js index ac510fa..47cee7f 100644 --- a/utils/getHTML.js +++ b/utils/getHTML.js @@ -5,7 +5,6 @@ import { env } from "../envs.js"; // ============================================ export function getSearchHtml() { - const GOOGLE_ENABLED = env.GOOGLE_API_KEY && env.GOOGLE_CX; const TOKEN_ENABLED = !!env.TOKEN; const DEFAULT_ENGINES = env.DEFAULT_ENGINES || []; const handlerEngineDefaultChecked = (engine) => @@ -94,7 +93,7 @@ export function getSearchHtml() {

- 基于 Cloudflare Workers 的多引擎聚合搜索服务。并行搜索 Google、DuckDuckGo、Bing、Brave 四大引擎,快速获取更全面的搜索结果。 + 基于 Cloudflare Workers 的多引擎聚合搜索服务。并行搜索 DuckDuckGo、Bing 等搜索引擎,快速获取更全面的搜索结果。

如果这个项目对你有帮助,可以 @@ -112,30 +111,14 @@ export function getSearchHtml() {

⚙️ 服务配置状态

-
- Google 搜索引擎 - - ${ - GOOGLE_ENABLED - ? "✓ 已配置" - : "○ 未配置 (需要 API Key)" - } - -
访问鉴权 - 其他引擎 (Brave/DuckDuckGo/Bing) + 搜索引擎 (DuckDuckGo/Bing) ✓ 可用
- ${ - !GOOGLE_ENABLED - ? ` -
-

- 💡 提示: 配置 Google API 后可使用 Google 搜索引擎。在 Cloudflare Dashboard 的 Worker 设置中添加环境变量:
- • GOOGLE_API_KEY - 获取 API Key
- • GOOGLE_CX - 获取 CX ID -

-
- ` - : "" - } ${ !TOKEN_ENABLED ? ` -
-

+

+

💡 建议:为防止服务被滥用,建议在 Cloudflare Dashboard 的 Worker 设置中添加环境变量 TOKEN 启用访问鉴权。

@@ -213,29 +173,6 @@ export function getSearchHtml() { 选择搜索引擎 (可多选)
- -
@@ -343,14 +274,16 @@ export function getSearchHtml() {
{
   "query": "cloudflare",              // 搜索关键词
   "number_of_results": 15,            // 结果总数
-  "enabled_engines": ["google", ...], // 启用的搜索引擎列表
+  "enabled_engines": ["duckduckgo", ...], // 启用的搜索引擎列表
   "unresponsive_engines": [],         // 无响应的搜索引擎列表
+  "empty_engines": [],                // 响应成功但无结果的搜索引擎列表
+  "engine_errors": {},                // 各搜索引擎错误信息
   "results": [
     {
       "title": "...",                 // 结果标题
       "description": "...",           // 结果描述
       "url": "...",                   // 结果链接
-      "engine": "google"              // 来源引擎
+      "engine": "duckduckgo"          // 来源引擎
     }
   ]
 }
@@ -365,21 +298,6 @@ export function getSearchHtml() { 🚀 支持的搜索引擎
-
-
-
Google
- ${ - GOOGLE_ENABLED - ? '✓ 可用' - : '○ 需配置' - } -
-

- 全球最大的搜索引擎,需要配置 Google Custom Search API -

-
DuckDuckGo
@@ -387,13 +305,6 @@ export function getSearchHtml() {

注重隐私保护的搜索引擎,无需配置

-
-
-
Brave Search
- ✓ 可用 -
-

独立的搜索引擎,无需配置

-
Bing
@@ -425,11 +336,6 @@ export function getSearchHtml() { 在 Cloudflare Dashboard → Workers & Pages → 您的 Worker → 设置 → 变量 中添加:

    - ${ - !GOOGLE_ENABLED - ? '
  • GOOGLE_API_KEYGOOGLE_CX - 启用 Google 搜索 (获取 API Key / 获取 CX)
  • ' - : "" - } ${ !TOKEN_ENABLED ? '
  • TOKEN - 启用访问鉴权 (建议)
  • ' @@ -613,7 +519,7 @@ export function getSearchHtml() { const tokenBodyParam = TOKEN_ENABLED && currentToken ? \`&token=\${currentToken}\` : ''; 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 = \`{ "mcpServers": { "cloudflare-search": { diff --git a/utils/searchBrave.js b/utils/searchBrave.js deleted file mode 100644 index 8e9c169..0000000 --- a/utils/searchBrave.js +++ /dev/null @@ -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; diff --git a/utils/searchGoogle.js b/utils/searchGoogle.js deleted file mode 100644 index 931ef07..0000000 --- a/utils/searchGoogle.js +++ /dev/null @@ -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; diff --git a/worker.js b/worker.js index fd85126..2632dd7 100644 --- a/worker.js +++ b/worker.js @@ -1,13 +1,9 @@ import { env, setEnv } from "./envs.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 searchBing from "./utils/searchBing.js"; const SEARCH_ENGINES = { - google: searchGoogle, - brave: searchBrave, duckduckgo: searchDuckDuckGo, bing: searchBing, }; @@ -23,12 +19,7 @@ function parseEngines(enginesParam) { return enginesParam .split(",") .map((e) => e.trim().toLowerCase()) - .filter((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); - }); + .filter((e) => env.SUPPORTED_ENGINES.includes(e)); } /** @@ -41,28 +32,35 @@ async function searchSingle(engineName, query) { const searchFn = SEARCH_ENGINES[engineName]; if (!searchFn) { console.warn(`Unknown engine: ${engineName}`); - return []; + return { results: [], error: "Unknown engine" }; } // 创建 AbortController 用于取消请求 const controller = new AbortController(); const timeout = parseInt(env.DEFAULT_TIMEOUT ?? "3000", 10); + let timeoutId; 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); - return result; + return { results, error: null }; } catch (error) { + const message = + error.name === "AbortError" + ? `Timeout after ${timeout}ms` + : error.message || String(error); + if (error.name === "AbortError") { - console.error(`[${engineName}] Timeout after ${timeout}ms`); + console.error(`[${engineName}] ${message}`); } 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 const results = []; const unresponsive = []; + const empty = []; + const errors = {}; resultsArr.forEach((result, index) => { const engineName = enabledEngines[index]; - if (result.status === "fulfilled" && result.value.length > 0) { + if (result.status === "fulfilled" && result.value.results.length > 0) { results.push( - ...result.value.map((item) => ({ + ...result.value.results.map((item) => ({ ...item, 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 { unresponsive.push(engineName); - if (result.status === "rejected") { - console.error(`[${engineName}] Rejected:`, result.reason); - } + errors[engineName] = result.reason?.message || String(result.reason); + console.error(`[${engineName}] Rejected:`, result.reason); } }); @@ -109,6 +113,8 @@ async function searchAll({ query, engines }) { number_of_results: results.length, enabled_engines: enabledEngines, unresponsive_engines: unresponsive, + empty_engines: empty, + engine_errors: errors, results, }; } diff --git a/wrangler.toml b/wrangler.toml index 4e7cf52..168c4e9 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -1,6 +1,3 @@ -# Cloudflare Worker 配置文件 -# 更多配置选项: https://developers.cloudflare.com/workers/wrangler/configuration/ - # Worker 名称(修改为你想要的名称) name = "cloudflare-search" @@ -15,45 +12,16 @@ compatibility_date = "2024-12-04" # 设置为 true 会自动分配一个 xxx.workers.dev 域名 workers_dev = true -# 账号 ID (可选,部署时会自动填充) -# account_id = "your-account-id" - -# ============================================ -# 路由配置(可选) -# ============================================ -# 如果你想将 Worker 绑定到自定义域名,取消注释并配置: -# routes = [ -# { pattern = "search.yourdomain.com/*", zone_name = "yourdomain.com" } -# ] # ============================================ # 环境变量(可选) # ============================================ [vars] -# Google Custom Search API 配置 -# GOOGLE_API_KEY = "" -# GOOGLE_CX = "" - # API 访问令牌(可选) # 配置后,所有 /search 请求都需要提供此 token 进行鉴权 # 可以通过 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 # ============================================ # 资源限制(可选)