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

@@ -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";

View File

@@ -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() {
</h1>
<div class="mt-6 text-base text-zinc-600 dark:text-zinc-400">
<p class="">
基于 Cloudflare Workers 的多引擎聚合搜索服务。并行搜索 Google、DuckDuckGo、Bing、Brave 四大引擎,快速获取更全面的搜索结果。
基于 Cloudflare Workers 的多引擎聚合搜索服务。并行搜索 DuckDuckGo、Bing 等搜索引擎,快速获取更全面的搜索结果。
</p>
<p class="mt-2">
如果这个项目对你有帮助,可以
@@ -112,30 +111,14 @@ export function getSearchHtml() {
<!-- 服务状态 -->
<div class="mt-8 rounded-2xl border ${
GOOGLE_ENABLED && !TOKEN_ENABLED
? "border-green-200 bg-green-50 dark:border-green-800/40 dark:bg-green-900/10"
: !GOOGLE_ENABLED || TOKEN_ENABLED
TOKEN_ENABLED
? "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">
<h2 class="text-sm font-semibold text-zinc-900 dark:text-zinc-100 mb-3">
⚙️ 服务配置状态
</h2>
<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">
<span class="text-zinc-700 dark:text-zinc-300">访问鉴权</span>
<span class="${
@@ -147,40 +130,17 @@ export function getSearchHtml() {
</span>
</div>
<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>
</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
? `
<div class="mt-4 pt-4 border-t ${
!GOOGLE_ENABLED
? "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"
}">
<div class="mt-4 pt-4 border-t border-green-200 dark:border-green-800/40">
<p class="text-xs text-green-700 dark:text-green-400">
💡 建议:为防止服务被滥用,建议在 Cloudflare Dashboard 的 Worker 设置中添加环境变量 <code class="px-1 py-0.5 ${
!GOOGLE_ENABLED
? "bg-amber-100 dark:bg-amber-900/30"
: "bg-green-100 dark:bg-green-900/30"
"bg-green-100 dark:bg-green-900/30"
} rounded">TOKEN</code> 启用访问鉴权。
</p>
</div>
@@ -213,29 +173,6 @@ export function getSearchHtml() {
选择搜索引擎 (可多选)
</label>
<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">
<input type="checkbox" name="engine" value="duckduckgo" ${handlerEngineDefaultChecked(
"duckduckgo"
@@ -251,12 +188,6 @@ export function getSearchHtml() {
<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">
<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>
@@ -343,14 +274,16 @@ export function getSearchHtml() {
<pre class="text-xs overflow-x-auto"><code>{
"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" // 来源引擎
}
]
}</code></pre>
@@ -365,21 +298,6 @@ export function getSearchHtml() {
🚀 支持的搜索引擎
</h2>
<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="flex items-center justify-between mb-2">
<div class="font-medium text-zinc-900 dark:text-zinc-100">DuckDuckGo</div>
@@ -387,13 +305,6 @@ export function getSearchHtml() {
</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">
<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="flex items-center justify-between mb-2">
<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 → 设置 → 变量 中添加:
</p>
<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
? '<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}\` : '';
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": {

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;