refactor(bf-bot): 插件重构为 handlers/services/utils 分层,资源归入 res/

- 旧平铺模块(data/data_utils/database_op/image_builder*/get_bf6_data/
  gametools_bf6/bf6_data/img_utils)拆分为 handlers(绑定/查询)、
  services(fetchers/processing/query)、utils(渲染/参数)分层;
  新增 repository.py / config.py / paths.py
- 字体/图片/模板资源迁入 res/,新增 BF2042 卡面素材(bg/object_icon 等)
- browser_ext/:BF2042 扩展捕获桥(WS 直连兜底通道)源码入库
- 新增 hexi/web/src/plugins/bf_bot 前端管理页
- 新增 tests:test_bf_bot_helpers / repository / tracker_api

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
sansenhoshiandClaude Code committed 2026-10-13 17:53:06 +08:00
1 parent 614e1d7e00
commit a31aab00e8
752 files changed
+6241 -2506

No files matched your search

@@ -0,0 +1,102 @@
// 主世界钩子(document_start):被动观察页面自己发出的请求,把命中的响应体副本
// postMessage 给 content.js。参照 FetchV / 猫抓一类捕获扩展的通行模式。
//
// 铁律:只观察,不改写请求与响应;任何异常都必须静默,绝不能影响页面本身。
(() => {
"use strict";
const MARKER = "__hexi_bf2042_capture__";
const API_PREFIX =
"https://api.tracker.gg/api/v2/bf2042/standard/profile/";
function match(url) {
return typeof url === "string" && url.startsWith(API_PREFIX);
}
function report(url, status, body) {
try {
window.postMessage({ [MARKER]: true, url, status, body }, "*");
} catch (_) {
/* 忽略:上报失败不能影响页面 */
}
}
// ---- fetch ----
// 用 Proxy 包一层:Function.prototype.toString.call() 显示为 native
// (Proxy 的 target 是原生函数),比"改写后补 toString"更难被识破。
if (typeof window.fetch === "function") {
const orig = window.fetch;
window.fetch = new Proxy(orig, {
apply(target, thisArg, args) {
const input = args[0];
const url =
typeof input === "string" ? input : (input && input.url) || "";
const result = Reflect.apply(target, thisArg, args);
if (match(url)) {
result
.then((resp) => {
try {
const ctype = resp.headers.get("content-type") || "";
if (!ctype.includes("json")) return;
// 立刻 clone,免得页面随后消费了原响应体
resp
.clone()
.json()
.then((body) => report(url, resp.status, body))
.catch(() => {});
} catch (_) {}
})
.catch(() => {});
}
return result;
},
});
}
// ---- XMLHttpRequest ----
const XHR = window.XMLHttpRequest;
if (XHR && XHR.prototype) {
const origOpen = XHR.prototype.open;
const origSend = XHR.prototype.send;
const urls = new WeakMap(); // 挂在实例上会留可见的自定义属性,用 WeakMap 更干净
XHR.prototype.open = new Proxy(origOpen, {
apply(target, thisArg, args) {
try {
urls.set(thisArg, String(args[1] ?? ""));
} catch (_) {}
return Reflect.apply(target, thisArg, args);
},
});
XHR.prototype.send = new Proxy(origSend, {
apply(target, thisArg, args) {
try {
const url = urls.get(thisArg);
if (match(url)) {
thisArg.addEventListener(
"loadend",
() => {
try {
const rt = thisArg.responseType;
let body = null;
if (rt === "" || rt === "text") {
body = JSON.parse(thisArg.responseText);
} else if (rt === "json") {
body = thisArg.response;
} else {
return; // arraybuffer / blob 等类型不处理
}
if (body != null) report(url, thisArg.status, body);
} catch (_) {}
},
{ once: true }
);
}
} catch (_) {}
return Reflect.apply(target, thisArg, args);
},
});
}
})();