Skip to content

Commit aba39cc

Browse files
author
bro-builder
committed
bro: 改为纯浏览器原生实现,移除 Node 本机服务
- 新增 MV3 扩展 bro/extension(background / bridge / site-agent): 借 host_permissions 做跨域静默抓取,并在后台标签页驱动 Kimi/DeepSeek 登录与对话 - 前端改走 window.postMessage 与扩展通信,搜索引擎解析移至 js/engines.js - 删除 bro/server(不再需要 Node/npm/Chromium),全程仅浏览器内运行 - 已用真实 Bing 结果页验证解析:网页 10 条、图片 33 条
1 parent 24966ca commit aba39cc

16 files changed

Lines changed: 856 additions & 3738 deletions

‎bro/.gitignore‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
# 依赖与本机运行产物(不要提交)
2-
node_modules/
3-
server/node_modules/
4-
server/ai/profiles/
5-
npm-debug.log*
6-
.DS_Store
1+
# 本机产物(不要提交)
2+
.DS_Store
3+
*.log

‎bro/css/style.css‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,21 @@ main { max-width: 1080px; margin: 0 auto; padding: 26px 22px 60px; }
232232
#view-search .favs { margin-top: 34px; }
233233
.foot { text-align: center; color: #aab0c0; font-size: 12px; padding: 20px 0 40px; }
234234

235+
/* 通用隐藏类(用于登录区/对话区/提示条切换) */
236+
.hidden { display: none !important; }
237+
238+
/* 未检测到扩展时的提示条 */
239+
.banner {
240+
background: #eef4ff; border: 1px solid #cfe0ff; color: #274690;
241+
border-radius: 12px; padding: 12px 14px; font-size: 13px;
242+
margin-bottom: 18px; line-height: 1.6;
243+
}
244+
245+
/* 扩展安装引导 */
246+
.guide-list { color: var(--muted); font-size: 13px; line-height: 1.9; padding-left: 20px; margin: 8px 0 0; }
247+
.guide-list code, .modal-card code { background: #f3f5fb; padding: 1px 5px; border-radius: 5px; font-size: 12px; }
248+
#guide .state { margin: 4px 0 12px; }
249+
235250
@media (max-width: 680px) {
236251
.brand-sub { display: none; }
237252
.hero-title { font-size: 30px; }

‎bro/extension/background.js‎

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// bro bridge —— 后台 service worker
2+
// ---------------------------------------------------------------------------
3+
// 只做两件事:
4+
// 1) 特权抓取:借 host_permissions,代表前端读取搜索引擎等站点的响应内容(不受 CORS 限制);
5+
// 2) 站点代理:在本机后台标签页中托管 Kimi / DeepSeek,并通过 content script 驱动其登录与对话。
6+
// 全部运行在浏览器内部,不依赖 Node / npm / Chromium 等外部环境。
7+
// ---------------------------------------------------------------------------
8+
9+
const SITES = {
10+
kimi: { url: 'https://www.kimi.com/' },
11+
deepseek: { url: 'https://chat.deepseek.com/' },
12+
};
13+
14+
const siteTabs = new Map(); // site -> tabId
15+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
16+
17+
/* ------------------------------ 特权抓取 ------------------------------ */
18+
async function fetchText(url) {
19+
const res = await fetch(url, {
20+
method: 'GET',
21+
credentials: 'include', // 带上本机浏览器已有的 Cookie,提高结果页可用性
22+
redirect: 'follow',
23+
cache: 'no-store',
24+
headers: { 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8' },
25+
});
26+
const text = await res.text();
27+
if (!res.ok) throw new Error(`目标站点返回 ${res.status}`);
28+
return { status: res.status, finalUrl: res.url, text };
29+
}
30+
31+
/* ------------------------------ 站点代理 ------------------------------ */
32+
async function ensureTab(site) {
33+
const cfg = SITES[site];
34+
if (!cfg) throw new Error(`未知站点:${site}`);
35+
36+
const existing = siteTabs.get(site);
37+
if (existing != null) {
38+
try {
39+
await chrome.tabs.get(existing);
40+
return existing;
41+
} catch {
42+
siteTabs.delete(site);
43+
}
44+
}
45+
46+
const tab = await chrome.tabs.create({ url: cfg.url, active: false });
47+
siteTabs.set(site, tab.id);
48+
return tab.id;
49+
}
50+
51+
// content script 可能尚未注入,带重试地发送消息
52+
async function sendToTab(tabId, message, { retries = 25, delay = 800 } = {}) {
53+
let lastErr;
54+
for (let i = 0; i < retries; i++) {
55+
try {
56+
return await chrome.tabs.sendMessage(tabId, message);
57+
} catch (err) {
58+
lastErr = err;
59+
await sleep(delay);
60+
}
61+
}
62+
throw new Error(`无法与站点页面通信:${lastErr?.message || lastErr}`);
63+
}
64+
65+
async function aiCall(site, cmd, payload = {}) {
66+
const tabId = await ensureTab(site);
67+
return sendToTab(tabId, { cmd, ...payload });
68+
}
69+
70+
/* --------------------------- 消息分发入口 --------------------------- */
71+
async function handle(method, params = {}) {
72+
switch (method) {
73+
case 'ping':
74+
return { pong: true };
75+
case 'fetch':
76+
return fetchText(params.url);
77+
78+
case 'ai.open':
79+
return aiCall(params.site, 'state');
80+
case 'ai.state':
81+
return aiCall(params.site, 'state');
82+
case 'ai.login':
83+
return aiCall(params.site, 'login', {
84+
username: params.username,
85+
password: params.password,
86+
captcha: params.captcha,
87+
});
88+
case 'ai.chat':
89+
return aiCall(params.site, 'chat', { text: params.text, images: params.images || [] });
90+
case 'ai.newChat':
91+
return aiCall(params.site, 'newChat');
92+
case 'ai.reset': {
93+
const tabId = siteTabs.get(params.site);
94+
if (tabId != null) {
95+
await chrome.tabs.remove(tabId).catch(() => {});
96+
siteTabs.delete(params.site);
97+
}
98+
return { ok: true };
99+
}
100+
default:
101+
throw new Error(`未知方法:${method}`);
102+
}
103+
}
104+
105+
chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
106+
if (!msg || msg.type !== 'bro') return undefined;
107+
handle(msg.method, msg.params)
108+
.then((data) => sendResponse({ ok: true, data }))
109+
.catch((err) => sendResponse({ ok: false, error: String(err?.message || err) }));
110+
return true; // 异步响应
111+
});
112+
113+
// 用户手动关闭托管标签页时同步清理映射
114+
chrome.tabs.onRemoved.addListener((tabId) => {
115+
for (const [site, id] of siteTabs.entries()) {
116+
if (id === tabId) siteTabs.delete(site);
117+
}
118+
});

‎bro/extension/bridge.js‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// bro bridge —— 注入到 bro 页面的内容脚本
2+
// ---------------------------------------------------------------------------
3+
// 作用:在“网页”和“扩展后台”之间转发消息。
4+
// 页面通过 window.postMessage 发请求,这里转交 service worker,再把结果回传页面。
5+
// ---------------------------------------------------------------------------
6+
(() => {
7+
const CHANNEL = 'bro-bridge-v1';
8+
9+
window.addEventListener('message', async (ev) => {
10+
if (ev.source !== window) return;
11+
if (ev.origin && ev.origin !== location.origin && ev.origin !== 'null') return;
12+
const msg = ev.data;
13+
if (!msg || msg.channel !== CHANNEL || msg.dir !== 'req') return;
14+
15+
try {
16+
const res = await chrome.runtime.sendMessage({
17+
type: 'bro',
18+
method: msg.method,
19+
params: msg.params || {},
20+
});
21+
if (res && res.ok) {
22+
window.postMessage({ channel: CHANNEL, dir: 'res', id: msg.id, ok: true, data: res.data }, location.origin);
23+
} else {
24+
window.postMessage(
25+
{ channel: CHANNEL, dir: 'res', id: msg.id, ok: false, error: res?.error || '扩展返回错误' },
26+
location.origin
27+
);
28+
}
29+
} catch (err) {
30+
window.postMessage(
31+
{ channel: CHANNEL, dir: 'res', id: msg.id, ok: false, error: String(err?.message || err) },
32+
location.origin
33+
);
34+
}
35+
});
36+
37+
// 告知页面:扩展已就绪
38+
window.postMessage({ channel: CHANNEL, dir: 'ready' }, location.origin);
39+
})();

‎bro/extension/manifest.json‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"manifest_version": 3,
3+
"name": "bro bridge",
4+
"version": "1.0.0",
5+
"description": "为 bro 搜索页提供浏览器原生特权接口:静默访问搜索引擎与 Kimi/DeepSeek,无需任何外部运行时。",
6+
"permissions": ["tabs", "scripting", "storage"],
7+
"host_permissions": [
8+
"https://*.bing.com/*",
9+
"https://www.baidu.com/*",
10+
"https://image.baidu.com/*",
11+
"https://www.sogou.com/*",
12+
"https://www.google.com/*",
13+
"https://duckduckgo.com/*",
14+
"https://html.duckduckgo.com/*",
15+
"https://www.kimi.com/*",
16+
"https://kimi.com/*",
17+
"https://*.moonshot.cn/*",
18+
"https://chat.deepseek.com/*",
19+
"https://*.deepseek.com/*"
20+
],
21+
"background": {
22+
"service_worker": "background.js"
23+
},
24+
"content_scripts": [
25+
{
26+
"matches": [
27+
"https://hicodehc.github.io/bro/*",
28+
"http://localhost/*",
29+
"http://127.0.0.1/*",
30+
"file:///*"
31+
],
32+
"js": ["bridge.js"],
33+
"run_at": "document_start"
34+
},
35+
{
36+
"matches": [
37+
"https://www.kimi.com/*",
38+
"https://kimi.com/*",
39+
"https://*.moonshot.cn/*",
40+
"https://chat.deepseek.com/*"
41+
],
42+
"js": ["site-agent.js"],
43+
"run_at": "document_idle",
44+
"all_frames": true
45+
}
46+
]
47+
}

0 commit comments

Comments
 (0)