diff --git a/README.md b/README.md index 241ed45..eabe1d5 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ - **轻量**:纯 PHP + SQLite,不依赖 MySQL / 框架 / Composer;前端无 Bootstrap / jQuery,原生 CSS + JS。 - **个人向**:单管理员,页面就是「你自己常用网站的起始页」。 -- **搜索引擎可切换 + 联想下拉**:后台勾选喜欢的搜索引擎即可显示在顶部;输入时下拉联想词由服务端代理(规避跨域)。后台的「显示/隐藏」按钮点一下**原地生效、不跳页**,且无 JS 时也能正常提交。 +- **搜索引擎可切换 + 联想下拉**:后台勾选喜欢的搜索引擎即可显示在顶部;输入时下拉建议词由服务端代理(规避跨域)。默认**联想词统一取百度、搜索走各自引擎**,并带自动兜底,任何引擎都保证有下拉词(详见下节实测表)。后台的「显示/隐藏」按钮点一下**原地生效、不跳页**,且无 JS 时也能正常提交。 - **添加即补全**:填网址后先访问该网址拿到标题,再按「高清优先」的顺序去网上找相关图标(`manifest` 512 → `apple-touch-icon` → `` → 第三方聚合服务),下载后统一规范化为 **128×128 PNG** 存到服务器本地。 - **图标可自定义,且自定义后不再被覆盖**:可为任一网站上传图标或直接填图标地址;一旦自定义,该网站的图标会被标记保护,「一键全部更新」只刷新它的名称、跳过图标。 - **显示简称**:每个网站有「名称(自动)」和「简称(手动)」两个字段,页面展示你自定义的简称。 @@ -58,20 +58,39 @@ SimpleNav/ > 需要额外说明的是:**服务器上若装了宝塔「网站加速」之类的页面缓存插件,本站也不需要为它做任何特殊配置**。 > 动态页发的 `Cache-Control: ... no-cache ...` 会被这类插件视为「不缓存」而直接跳过写入。 -## 搜索引擎联想接口说明 +## 搜索联想(建议词)说明 -联想接口需服务端代理(浏览器直接调用会跨域)。内置解析类型: +联想接口必须由**服务端代理**(浏览器直接调用会跨域)。2026-09-28 在本站服务器实测各家的可用性: -| 类型 | 平台 | 联想接口 | +| 引擎 | 联想接口 | 实测 | 结论 | +|---|---|---|---| +| 百度 | `www.baidu.com/sugrec?prod=pc&wd={q}` | 200 / 0.12s | ✅ 最快最稳 | +| 360 | `sug.so.360.cn/suggest?...&word={q}` | 200 / 0.20s | ✅ | +| 必应 | `api.bing.com/osjson.aspx?query={q}` | 200 / 0.5~1.6s | ✅ 可用但明显慢 | +| 搜狗 | `www.sogou.com/suggnew/ajajjson?...` | 200 / **0 字节** | ❌ 已失效(联想域名不再解析) | +| 谷歌 | `suggestqueries.google.com/complete/search?...` | 连接超时 | ❌ 国内不可达 | +| DuckDuckGo | `duckduckgo.com/ac/?...` | 连接超时 | ❌ 国内不可达 | + +因此默认策略是——**联想词统一取百度,搜索仍走各自引擎**:点哪个引擎就用哪个引擎去搜, +只是输入框下面的建议来自百度,这样切到任何引擎都有下拉词。 + +后台「设置 → 联想词来源」可切换:`百度`(默认)/ `必应` / `360` / `各搜索引擎自己的接口`。 +任何情况下都带**自动兜底链**:首选来源取不到词(接口挂了、超时、返回空)就依次回退 +`百度 → 必应 → 360`,所以下拉框不会因为某家接口失效而变空。 + +> 想在某个引擎上恢复「用自己的联想接口」,把来源改成「各搜索引擎自己的接口」, +> 再在该引擎的「联想接口 / 联想解析类型」里填好地址即可(解析类型见下)。 + +内置的解析类型(`suggest_type`): + +| 类型 | 说明 | 返回形态 | |---|---|---| -| `bing_osjson` | 必应 | `https://api.bing.com/osjson.aspx?query={q}` | -| `baidu_sugrec` | 百度 | `https://www.baidu.com/sugrec?prod=pc&wd={q}` | -| `google_firefox` | 谷歌 | `https://suggestqueries.google.com/complete/search?client=firefox&q={q}` | -| `sogou_ajaj` | 搜狗 | `https://www.sogou.com/suggnew/ajajjson?type=web&key={q}` | -| `so360` | 360 | `https://sug.so.360.cn/suggest?encodein=utf-8&encodeout=utf-8&format=json&word={q}` | -| `ddg` | DuckDuckGo | `https://duckduckgo.com/ac/?type=list&q={q}` | - -> 注:国内服务器访问 Google / DuckDuckGo 可能不通,联想会优雅降级为空(不影响搜索跳转)。 +| `baidu_sugrec` | 百度 | `{"g":[{"q":"…"}]}` | +| `so360` | 360 | `{"result":[{"word":"…"}]}` | +| `bing_osjson` | 必应 | `["q",["s1","s2"]]` | +| `google_firefox` | 谷歌 | `["q",["s1","s2"]]` | +| `sogou_ajaj` | 搜狗 | `{"items":[…]}`(GBK,已自动转码) | +| `ddg` | DuckDuckGo | `[{"phrase":"…"}]` | ## 许可 diff --git a/admin/actions.php b/admin/actions.php index b51f909..2f106ee 100644 --- a/admin/actions.php +++ b/admin/actions.php @@ -208,6 +208,8 @@ try { /* ---------- 设置 ---------- */ case 'settings_save': { + $srcRaw = (string)($_POST['suggest_source'] ?? 'baidu'); + $src = in_array($srcRaw, ['auto', 'baidu', 'bing', 'so360'], true) ? $srcRaw : 'baidu'; $map = [ 'site_name' => trim((string)($_POST['site_name'] ?? '')), 'subtitle' => trim((string)($_POST['subtitle'] ?? '')), @@ -215,6 +217,7 @@ try { 'primary_color' => preg_match('/^#[0-9a-fA-F]{3,8}$/', (string)($_POST['primary_color'] ?? '')) ? $_POST['primary_color'] : '#2F6BF7', 'open_blank' => isset($_POST['open_blank']) ? '1' : '0', 'icon_services' => isset($_POST['icon_services']) ? '1' : '0', + 'suggest_source' => $src, ]; $st = $pdo->prepare('INSERT INTO settings (k,v) VALUES (?,?) ON CONFLICT(k) DO UPDATE SET v=excluded.v'); foreach ($map as $k => $v) { $st->execute([$k, $v]); } diff --git a/admin/index.php b/admin/index.php index 16ce218..61ee38f 100644 --- a/admin/index.php +++ b/admin/index.php @@ -241,6 +241,11 @@ elseif ($p === 'engines'): ?> +
+ 这两项只在「设置 → 联想词来源」选各搜索引擎自己的接口时才会用到; + 默认是统一用百度出建议(搜索本身仍走各自的搜索地址)。 + 这里填的接口只要通了就会被优先使用,没通就自动回退到百度。 +
@@ -296,6 +301,26 @@ else: // settings ?> + ++ 搜索时仍然用你选中的那个引擎,这里只决定下拉建议词从哪儿来。 + 实测(2026-09-28):百度 0.12s、360 0.20s、必应 0.5~1.6s 可用; + 搜狗接口已失效(返回 0 字节)、谷歌与 DuckDuckGo 国内连不上。 + 所以默认「统一用百度」——这样切到任何引擎下拉都有词。选「各搜索引擎自己的接口」则恢复成 + 各用各的(没恢复好的引擎会自动回退到百度的建议)。 +
diff --git a/assets/js/app.js b/assets/js/app.js index d6378ca..4d01363 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -18,6 +18,8 @@ if (form) { form.setAttribute('action', search); form.setAttribute('data-slug', slug); } if (input) { input.setAttribute('name', param); input.setAttribute('placeholder', ph); } hideWord(); + // 已经输了字就顺手换一份建议(auto 模式下各引擎来源不同) + if (input && input.value.trim() !== '') { askSuggest(); } } tabs.forEach(function (el) { el.addEventListener('click', function () { activateEngine(el); input && input.focus(); }); @@ -26,7 +28,8 @@ /* ---------- 联想下拉 ---------- */ var word = document.getElementById('word'); var timer = null; - var lastQ = ''; + var lastQ = null; + var seq = 0; // 请求序号:防止先发的慢响应覆盖后发的快响应 function hideWord() { if (word) { word.innerHTML = ''; word.style.display = 'none'; } } function showWord(items) { @@ -40,20 +43,24 @@ word.style.display = 'block'; } + function askSuggest() { + if (!input) return; + var q = input.value.trim(); + if (q === '') { lastQ = null; hideWord(); return; } + if (q === lastQ) { return; } + lastQ = q; + var mine = ++seq; + var slug = form ? (form.getAttribute('data-slug') || '') : ''; + fetch(cfg.suggest + '?slug=' + encodeURIComponent(slug) + '&q=' + encodeURIComponent(q)) + .then(function (r) { return r.json(); }) + .then(function (data) { if (mine === seq) { showWord(data && data.items); } }) + .catch(function () { if (mine === seq) { hideWord(); } }); + } + if (input) { input.addEventListener('input', function () { - var q = input.value.trim(); - if (q === '') { hideWord(); return; } - if (q === lastQ) return; - lastQ = q; clearTimeout(timer); - timer = setTimeout(function () { - var slug = form ? (form.getAttribute('data-slug') || '') : ''; - fetch(cfg.suggest + '?slug=' + encodeURIComponent(slug) + '&q=' + encodeURIComponent(q)) - .then(function (r) { return r.json(); }) - .then(function (data) { showWord(data && data.items); }) - .catch(function () { hideWord(); }); - }, 180); + timer = setTimeout(askSuggest, 180); }); input.addEventListener('keydown', function (e) { diff --git a/db.php b/db.php index e386f03..74c32e2 100644 --- a/db.php +++ b/db.php @@ -81,6 +81,12 @@ SQL); $pdo->exec('ALTER TABLE sites ADD COLUMN icon_custom INTEGER NOT NULL DEFAULT 0'); } + // 老库升级:清掉「搜狗联想」这个已经彻底失效的默认值 + // (2026-09-28 实测:suggnew/ajajjson 返回 200 但 0 字节,联想域名 w.sugg.sogou.com 已不解析) + // 只匹配默认种子里的那个 URL —— 用户自己改过的地址绝不动。 + $pdo->prepare('UPDATE engines SET suggest_url=?, suggest_type=? WHERE slug=? AND suggest_url=?') + ->execute(['', '', 'sogou', 'https://www.sogou.com/suggnew/ajajjson?type=web&key={q}']); + // 是否为空库(用于判断是否写入默认数据) $isEmpty = (int)$pdo->query('SELECT COUNT(*) FROM groups')->fetchColumn() === 0; @@ -112,13 +118,16 @@ function seed_engines(PDO $pdo): void return; } // name, slug, search_url, param, suggest_url, suggest_type, color, active, sort + // ⚠️ suggest_url 留空的含义:该引擎自己没有可用的联想接口。 + // 反正默认「联想词来源 = 百度」,留空只会让 auto 模式下少一次无谓的超时等待。 + // 搜狗(suggnew/ajajjson 已失效)、谷歌 / DuckDuckGo(国内不可达)都不填。 $rows = [ ['必应', 'bing', 'https://cn.bing.com/search', 'q', 'https://api.bing.com/osjson.aspx?query={q}', 'bing_osjson', '#60A504', 1, 1], ['百度', 'baidu', 'https://www.baidu.com/s', 'wd', 'https://www.baidu.com/sugrec?prod=pc&wd={q}', 'baidu_sugrec', '#2100E0', 1, 2], - ['谷歌', 'google', 'https://www.google.com/search', 'q', 'https://suggestqueries.google.com/complete/search?client=firefox&q={q}', 'google_firefox','#3B83FA', 0, 3], - ['搜狗', 'sogou', 'https://www.sogou.com/web', 'query', 'https://www.sogou.com/suggnew/ajajjson?type=web&key={q}', 'sogou_ajaj', '#FE6B14', 0, 4], + ['谷歌', 'google', 'https://www.google.com/search', 'q', '', '', '#3B83FA', 0, 3], + ['搜狗', 'sogou', 'https://www.sogou.com/web', 'query', '', '', '#FE6B14', 0, 4], ['360', 'so360', 'https://www.so.com/s', 'q', 'https://sug.so.360.cn/suggest?encodein=utf-8&encodeout=utf-8&format=json&word={q}', 'so360', '#1BB04A', 0, 5], - ['DuckGo','ddg', 'https://duckduckgo.com/', 'q', 'https://duckduckgo.com/ac/?type=list&q={q}', 'ddg', '#DE5833', 0, 6], + ['DuckGo','ddg', 'https://duckduckgo.com/', 'q', '', '', '#DE5833', 0, 6], ]; $stmt = $pdo->prepare('INSERT INTO engines (name,slug,search_url,param,suggest_url,suggest_type,color,is_active,sort_order) VALUES (?,?,?,?,?,?,?,?,?)'); foreach ($rows as $r) { diff --git a/index.php b/index.php index 81ab1bc..e8b5567 100644 --- a/index.php +++ b/index.php @@ -103,7 +103,8 @@ function site_tile(array $s): void -