虎牙人工兑换改用本机浏览器并新增扩展自动注入登录态
移除服务器浏览器方案(容器/Xvfb/noVNC/CDP 会话管理),后端仅下发 兑换材料;新增内置 Chrome/Edge 扩展一键写入 huya.com 登录态, 未安装扩展时回退控制台脚本手动注入,兑换始终由用户手动完成。
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
// 虎牙兑换助手后台:接收后台页面 relay 转发的登录材料,
|
||||
// 覆盖写入 huya.com 同名 Cookie 后打开已登录的兑换页。
|
||||
const HUYA_COOKIE_URL = 'https://www.huya.com/';
|
||||
const TTL_SECONDS = 180 * 86400;
|
||||
|
||||
function badge(tabId, text) {
|
||||
if (!tabId) return;
|
||||
chrome.action.setBadgeText({ tabId, text });
|
||||
setTimeout(() => {
|
||||
chrome.action.setBadgeText({ tabId, text: '' });
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
async function injectKit(kit) {
|
||||
const cookies = Array.isArray(kit && kit.cookies) ? kit.cookies : [];
|
||||
const wanted = new Set(cookies.map(([name]) => String(name)).filter(Boolean));
|
||||
// 只清理与登录态同名的旧 Cookie(覆盖所有域/路径变体),其余 huya Cookie 保持不动,
|
||||
// 行为与控制台脚本方案一致,避免误伤浏览器里已有的其他虎牙状态。
|
||||
const existing = await chrome.cookies.getAll({ domain: 'huya.com' });
|
||||
for (const cookie of existing) {
|
||||
if (!wanted.has(cookie.name)) continue;
|
||||
const url = 'https://' + cookie.domain.replace(/^\./, '') + (cookie.path || '/');
|
||||
try {
|
||||
await chrome.cookies.remove({ url, name: cookie.name });
|
||||
} catch (e) { /* 单个失败不阻断 */ }
|
||||
}
|
||||
const expirationDate = Math.floor(Date.now() / 1000) + TTL_SECONDS;
|
||||
for (const [name, value] of cookies) {
|
||||
if (!name) continue;
|
||||
try {
|
||||
await chrome.cookies.set({
|
||||
url: HUYA_COOKIE_URL,
|
||||
domain: '.huya.com',
|
||||
path: '/',
|
||||
name: String(name),
|
||||
value: value == null ? '' : String(value),
|
||||
secure: true,
|
||||
expirationDate,
|
||||
});
|
||||
} catch (e) { /* 单个失败不阻断 */ }
|
||||
}
|
||||
if (kit && kit.target_url) {
|
||||
chrome.tabs.create({ url: kit.target_url });
|
||||
}
|
||||
}
|
||||
|
||||
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
if (msg && msg.type === 'inject' && msg.kit) {
|
||||
injectKit(msg.kit).then(() => badge(sender.tab && sender.tab.id, 'OK'));
|
||||
sendResponse({ ok: true });
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
// 兜底:后台页面把材料存到 window.__huyaExchangeKit,点击扩展图标时读取。
|
||||
chrome.action.onClicked.addListener(async (tab) => {
|
||||
if (!tab || !tab.id) return;
|
||||
let payload = null;
|
||||
try {
|
||||
const [result] = await chrome.scripting.executeScript({
|
||||
target: { tabId: tab.id },
|
||||
func: () => window.__huyaExchangeKit || null,
|
||||
});
|
||||
payload = result ? result.result : null;
|
||||
} catch (e) {
|
||||
return; // 无权访问当前页面
|
||||
}
|
||||
if (!payload) {
|
||||
badge(tab.id, '无任务');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await injectKit(JSON.parse(payload));
|
||||
badge(tab.id, 'OK');
|
||||
} catch (e) {
|
||||
badge(tab.id, '失败');
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
"""生成虎牙兑换助手扩展图标:橙色渐变圆角底 + 白色礼盒。
|
||||
|
||||
128/48 用完整图案;16px 细节会糊,改用去掉蝴蝶结的加粗简化版。
|
||||
用法: uv run python web/extension/huya-exchange-helper/generate_icons.py
|
||||
"""
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
S = 1024 # 超采样画布,最后缩到目标尺寸
|
||||
RADIUS = 200
|
||||
|
||||
TOP = (255, 156, 71) # 渐变顶部
|
||||
BOTTOM = (240, 84, 0) # 渐变底部
|
||||
BOX = (255, 255, 255) # 礼盒
|
||||
RIBBON = (234, 74, 8) # 丝带(比背景深,压在白色上可见)
|
||||
|
||||
|
||||
def gradient_rounded(size: int, radius: int) -> Image.Image:
|
||||
grad = Image.new('RGBA', (size, size))
|
||||
g = ImageDraw.Draw(grad)
|
||||
for y in range(size):
|
||||
t = y / (size - 1)
|
||||
color = tuple(round(TOP[i] + (BOTTOM[i] - TOP[i]) * t) for i in range(3)) + (255,)
|
||||
g.line([(0, y), (size, y)], fill=color)
|
||||
mask = Image.new('L', (size, size), 0)
|
||||
ImageDraw.Draw(mask).rounded_rectangle([0, 0, size, size], radius=radius, fill=255)
|
||||
out = Image.new('RGBA', (size, size), (0, 0, 0, 0))
|
||||
out.paste(grad, (0, 0), mask)
|
||||
return out
|
||||
|
||||
|
||||
def draw_gift(img: Image.Image) -> None:
|
||||
d = ImageDraw.Draw(img)
|
||||
cx = S // 2
|
||||
# 蝴蝶结:左右两个椭圆
|
||||
d.ellipse([348, 235, 512, 365], fill=BOX)
|
||||
d.ellipse([512, 235, 676, 365], fill=BOX)
|
||||
# 盒盖(比盒身宽)
|
||||
d.rounded_rectangle([cx - 235, 375, cx + 235, 485], radius=30, fill=BOX)
|
||||
# 盒身
|
||||
d.rounded_rectangle([cx - 200, 509, cx + 200, 789], radius=36, fill=BOX)
|
||||
# 竖向丝带贯穿盖与盒身
|
||||
d.rectangle([cx - 44, 375, cx + 44, 789], fill=RIBBON)
|
||||
# 结口圆点,压在蝴蝶结与盒盖交界
|
||||
d.ellipse([cx - 46, 330, cx + 46, 422], fill=RIBBON)
|
||||
|
||||
|
||||
def draw_gift_simple(img: Image.Image) -> None:
|
||||
"""小尺寸版:去掉蝴蝶结,盒体加粗。"""
|
||||
d = ImageDraw.Draw(img)
|
||||
cx = img.width // 2
|
||||
scale = img.width / 512
|
||||
def rect(x1, y1, x2, y2, radius):
|
||||
d.rounded_rectangle([x1 * scale, y1 * scale, x2 * scale, y2 * scale],
|
||||
radius=radius * scale, fill=BOX)
|
||||
rect(cx / scale - 144, 96, cx / scale + 144, 206, 24) # 盖
|
||||
rect(cx / scale - 112, 230, cx / scale + 112, 420, 28) # 盒身
|
||||
d.rectangle([cx - 34 * scale, 96 * scale, cx + 34 * scale, 420 * scale], fill=RIBBON)
|
||||
|
||||
|
||||
full = gradient_rounded(S, RADIUS)
|
||||
draw_gift(full)
|
||||
base = 'web/extension/huya-exchange-helper/'
|
||||
for size in (128, 48):
|
||||
full.resize((size, size), Image.LANCZOS).save(f'{base}icon{size}.png')
|
||||
|
||||
simple = gradient_rounded(512, 100)
|
||||
draw_gift_simple(simple)
|
||||
simple.resize((16, 16), Image.LANCZOS).save(f'{base}icon16.png')
|
||||
print('icons regenerated')
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 653 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "虎牙兑换助手",
|
||||
"version": "1.0.0",
|
||||
"description": "配合直播后台使用:一键把所选虎牙账号的登录态写入本机浏览器(仅 huya.com 域),兑换操作仍由用户手动完成。",
|
||||
"permissions": ["cookies", "scripting", "activeTab"],
|
||||
"host_permissions": ["*://*.huya.com/*"],
|
||||
"background": {
|
||||
"service_worker": "background.js"
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["<all_urls>"],
|
||||
"js": ["relay.js"],
|
||||
"run_at": "document_start"
|
||||
}
|
||||
],
|
||||
"action": {
|
||||
"default_title": "虎牙兑换助手"
|
||||
},
|
||||
"icons": {
|
||||
"16": "icon16.png",
|
||||
"48": "icon48.png",
|
||||
"128": "icon128.png"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// 内容脚本:在所有页面标记扩展存在,并把后台页面派发的兑换材料转发给 service worker。
|
||||
// 后台页面通过 documentElement.dataset.huyaExchangeHelper === '1' 检测扩展是否可用。
|
||||
document.documentElement.dataset.huyaExchangeHelper = '1';
|
||||
|
||||
window.addEventListener('huya-exchange-kit', (event) => {
|
||||
const detail = event.detail;
|
||||
if (typeof detail !== 'string') return;
|
||||
try {
|
||||
const kit = JSON.parse(detail);
|
||||
if (kit && kit.target_url && Array.isArray(kit.cookies)) {
|
||||
const sending = chrome.runtime.sendMessage({ type: 'inject', kit });
|
||||
if (sending && typeof sending.catch === 'function') {
|
||||
sending.catch(() => { /* 扩展上下文失效等场景忽略 */ });
|
||||
}
|
||||
}
|
||||
} catch (e) { /* 忽略非法消息 */ }
|
||||
}, true);
|
||||
Reference in New Issue
Block a user