feat(huya): 浏览器兑换三目标直达并收窄扩展注入范围

- 操作列改为「直播间 / 精英宝典 / 电竞宝典」三个直达按钮,点哪个就按哪个目标写入登录态并打开,去掉弹窗内选择
- 新增 services/huya_exchange_targets.py 统一三个目标地址,接口支持可选 body {"target": "..."}(不传=精英宝典)
- 扩展内容脚本从 <all_urls> 收窄到后台站点白名单,service worker 校验 sender.tab.url,拒绝其它来源的注入
- 注入改为 Cookie 写完后回执(ok/written/failed/error),前端按回执提示成功或失败,不再静默
- 扩展图标兜底改用 world:'MAIN' 读取;一个 Cookie 都没写成功时不打开目标页
- 兑换材料签发写审计 huya:exchange:kit(含账号与目标)
- 控制台脚本增加 huya.com 域校验与 Secure,与扩展路径保持一致
- 修复兑换记录轮询的跨账号串扰与卸载后 setState;兑换请求加 epoch 防止过期结果覆盖弹窗
- 新增 tests/test_huya_browser_exchange.py(13 项)并同步 docs/HUYA_BROWSER_EXCHANGE.md
This commit is contained in:
yml2213
2026-09-11 10:31:46 +08:00
parent 874a95dd6f
commit 4518b726ac
14 changed files with 838 additions and 102 deletions
+39 -13
View File
@@ -10,6 +10,20 @@
- 列表按用户隔离保存(工作台 scope `browser_exchange`),与精英宝典的账号集合互不影响。
- 账号需已有 Cookie 才能执行兑换;账号的 App 协议登录由管理员在账号管理页(直链 `/huya/accounts`)完成,客服遇到无 Cookie 账号时联系管理员处理。
## 目标页三个直达按钮
操作列直接提供三个目标按钮(直播间 / 精英宝典 / 电竞宝典),点哪个就按哪个目标写入登录态并打开对应页面;接口不传 `target` 时用「精英宝典」(与历史行为一致)。
| key | 名称 | 地址 |
| --- | --- | --- |
| `live_room` | 直播间 | `https://www.huya.com/30596253` |
| `elite` | 精英宝典(默认) | `https://zt.huya.com/b02faae1/pc/index.html?sourceId=yellowcarlist&pid={room_pid}&anchorUid={room_pid}&roomid=30596253` |
| `esports` | 电竞宝典 | `https://zt.huya.com/50ea007f/pc/index.html?sourceId=yellowcarlist&pid={room_pid}&anchorUid={room_pid}&roomid=30596253` |
- `{room_pid}` 取虎牙配置的 `room_pid`;配置为空时用默认 `1199650619883`(`web/backend/huya_defaults.py`)。直播间地址与 `room_pid` 无关。
- 每次点击都会按该目标重新取一次兑换材料(登录脚本里的跳转地址跟着目标变)。
- 新增目标:在 `web/backend/services/huya_exchange_targets.py` 加常量与地址,并在 `web/backend/schemas.py` 的 `HuyaBrowserExchangeRequest.target` 正则里补上 key(`tests/test_huya_browser_exchange.py` 会校验两处不漂移)。
## 一键自动注入(推荐):安装内置扩展
项目自带 Chrome/Edge 扩展 `web/extension/huya-exchange-helper/`,安装一次后即可自动注入:
@@ -17,14 +31,17 @@
1. 打开扩展管理页:Chrome 为 `chrome://extensions`,Edge 为 `edge://extensions`。
2. 开启「开发者模式」,点击「加载已解压的扩展程序」,选择仓库目录 `web/extension/huya-exchange-helper`。
3. 刷新后台页面(扩展的内容脚本需要对当前页生效)。
- 已装过旧版扩展时,升级后必须回到 `chrome://extensions` 点一次「刷新」;否则浏览器里跑的还是旧版(旧版会注入所有网站,本次安全收窄不生效)。
之后的流程:
1. 在兑换列表点击账号的「浏览器兑换」→ 后端返回兑换材料 → 扩展自动把该账号 Cookie 写入 `.huya.com` 域并在新标签页打开已登录的兑换页。
2. 确认页面右上角账号无误后,手动完成兑换。
3. 回到后台点击「完成」。
1. 在兑换列表操作列点击目标按钮(直播间 / 精英宝典 / 电竞宝典)→ 后端返回兑换材料 → 扩展自动把该账号 Cookie 写入 `.huya.com` 域并在新标签页打开该页面;若一个 Cookie 都没写成功,扩展**不会**打开目标页(避免打开未登录或上一个账号的页面),弹窗显示失败原因。
2. 弹窗会显示本次注入结果(写入 / 失败条数);失败时按提示处理后点「注入登录态并打开」重试。
3. 确认页面右上角账号无误后,手动完成兑换,回到后台点击「完成」。
扩展只在注入时覆盖与登录态同名的旧 Cookie,其余虎牙 Cookie 不动;扩展图标点击可作为兜底(读取当前后台页签留存的材料);未检测到材料时图标会显示「无任务」角标。
扩展只在注入时覆盖与登录态同名的旧 Cookie,其余虎牙 Cookie 不动;扩展图标点击可作为兜底(读取当前**后台页签**留存的材料),未检测到材料时图标显示「无任务」角标。
内容脚本只注入后台站点白名单(`manifest.json` 的 `content_scripts.matches`):生产 `https://live.khhao.com`、开发隧道 `https://www.u499731.nyat.app`、本地 `http://localhost:5174`、`http://127.0.0.1:5174`、`http://localhost:8800`、`http://127.0.0.1:8800`。若后台使用其它域名,需要把该域名加进名单再重新加载扩展——service worker 从这份名单推导允许触发注入的来源,其它站点派发的注入请求会被拒绝。
## 查询兑换记录
@@ -37,35 +54,44 @@
## 手动方式(未装扩展时的兜底)
1. 在兑换列表点击「浏览器兑换」,弹出「浏览器兑换」窗口。
2. 点击「打开兑换页面」在新标签页打开兑换活动页(地址来自配置的 `room_pid`,未配置时回落到活动页 `https://zt.huya.com/b02faae1/pc/index.html`)。
3. 点击「复制登录脚本」,在兑换页面按 `F12` 打开控制台,粘贴脚本并回车执行。Chrome/Edge 首次粘贴需先手动输入 `allow pasting` 再回车。脚本把该账号 Cookie 写入 `.huya.com` 域并自动跳转。
4. 确认登录态正确后完成兑换,回到系统点击「完成」。
1. 在兑换列表操作列点击目标按钮(直播间 / 精英宝典 / 电竞宝典)→ 未装扩展时目标页会直接在新标签页打开,并弹出「浏览器兑换」窗口给出手动兜底。
2. 在**虎牙域下的页面**按 `F12` 打开控制台,粘贴脚本并回车执行。Chrome/Edge 首次粘贴需先手动输入 `allow pasting` 再回车。脚本会先校验当前页面在 `huya.com` 域下(不满足会弹窗提示并中止),写入带 `Secure`(与扩展路径一致)的 Cookie 后跳转目标页。
3. 确认登录态正确后完成兑换,回到系统点击「完成」。
没有控制台操作习惯时,可安装 Cookie-editor 类扩展,使用「复制 Cookie 字符串」在 `.huya.com` 域下导入。
## 接口
`POST /api/huya/accounts/{account_id}/browser-exchange`(权限 `huya:task`)返回:
`POST /api/huya/accounts/{account_id}/browser-exchange`(权限 `huya:task`),可选 body `{"target": "live_room" | "elite" | "esports"}`:
```json
{
"account_id": 1,
"username": "xxx",
"target": "elite",
"targets": [
{ "key": "live_room", "label": "直播间", "url": "https://www.huya.com/30596253" },
{ "key": "elite", "label": "精英宝典", "url": "https://zt.huya.com/b02faae1/pc/index.html?..." },
{ "key": "esports", "label": "电竞宝典", "url": "https://zt.huya.com/50ea007f/pc/index.html?..." }
],
"target_url": "https://...",
"cookie_string": "yyuid=...; ...",
"cookie_script": "(function(){...})();"
}
```
- 脚本由 `core.huya.cookie_utils.browser_login_script` 生成:Cookie 以 JSON 数组内联(自动转义),逐条 `document.cookie` 写入 `domain=.huya.com; path=/`,有效期 180 天,最后跳转目标页。
- 扩展注入走同一条接口:前端解析 `cookie_string` 后通过 DOM 事件交给扩展,由 `chrome.cookies` API 写入。
- `targets` 固定返回三个可选目标(顺序:直播间、精英宝典、电竞宝典),前端据此渲染选择器;非法 `target` 由 Pydantic 正则拦下(422)。
- 脚本由 `core.huya.cookie_utils.browser_login_script` 生成:先校验 `location.hostname` 属于 `huya.com`,Cookie 以 JSON 数组内联(自动转义),逐条 `document.cookie` 写入 `domain=.huya.com; path=/; secure`,有效期 180 天,最后跳转 `target_url`。
- 扩展注入走同一条接口:前端解析 `cookie_string` 后通过 DOM 事件交给扩展,由 `chrome.cookies` API 写入,并把写入结果回执给页面(前端据此提示成功/失败,不再静默)。
- 兑换账号列表复用 `/api/huya/workbench-accounts`(`handbook_scope=browser_exchange`)。
- 每次签发都会留审计日志:`audit_logs.action = huya:exchange:kit`,`target = huya_account:{id}`,detail 含 `account_id` 与 `target`。
- 服务端不保存任何会话状态。
## 安全说明
- 接口受 `huya:task` 权限与账号可见范围约束;脚本、Cookie 字符串与扩展注入等同账号凭证,仅在授权用户点击时下发。
- 接口受 `huya:task` 权限与账号可见范围约束;脚本、Cookie 字符串与扩展注入等同账号凭证,仅在授权用户点击时下发,且逐次留痕。
- 登录态只写入操作者自己的浏览器,不会在服务器留存浏览器进程或 Profile。
- 扩展为内部工具,通过开发者模式加载;其内容脚本在所有页面注入通信桥(manifest `<all_urls>`),但只响应本后台页面派发的固定事件,且只写 `huya.com` 域的 Cookie。
- 扩展为内部工具,通过开发者模式加载;内容脚本只注入后台站点白名单,service worker 二次校验消息来源站点,且只写 `huya.com` 域的 Cookie。
- 仍存在的边界:注入请求由后台页面用 `CustomEvent` 派发,页面内其它脚本理论上可以监听或伪造同名事件(页面派发的事件 `isTrusted` 恒为 false,无法据此区分真伪);因此不要把不受信任的脚本引入后台页面。
- 扩展写入的 Cookie 非 `HttpOnly`(`chrome.cookies` 限制),虎牙域内脚本可读,这是本方案的固有取舍。
- 请勿把脚本、Cookie 或扩展转给无关人员;如需共享操作,建议使用系统的账号授权机制而非转发凭证。