fix(desktop): extract real account name for wenxin/yuanbao/deepseek binding
The generic AI page-state heuristic only catches displayName via DOM patterns
like [class*="user-name"], so wenxin/yuanbao/deepseek (CSS-module hashed
classes or no exposed store) fell back to the placeholder "${label} 会话".
Add per-platform readers under adapters/{wenxin,yuanbao,deepseek}/account-identity.ts:
- wenxin: GET /eb/user/info with BDUSS cookie; fall back to __NEXT_REDUX_STORE__
and sidebar DOM if the API fails or hasn't responded yet.
- yuanbao: poll the stable BEM .yb-nav__user .nick-info-name + avatar img,
since yuanbao exposes no user-info API and the sidebar renders late.
- deepseek: poll localStorage for userToken, then call /api/v0/users/current
with Authorization: Bearer <token>; reads biz_data.id_profile.{name,picture}.
detectWenxin/Yuanbao/DeepSeekPlatform now run the credential-backed detector
first, then override the placeholder displayName/avatar via the per-platform
reader. Existing rows in DB still hold the placeholder until the account is
re-bound — probe does not write back display_name yet.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import type { WebContents } from 'electron/main'
|
||||
|
||||
export interface YuanbaoAccountIdentityHints {
|
||||
displayName: string | null
|
||||
avatarUrl: string | null
|
||||
}
|
||||
|
||||
const POLL_TIMEOUT_MS = 6_000
|
||||
const POLL_INTERVAL_MS = 250
|
||||
|
||||
function normalizeText(value: unknown): string | null {
|
||||
if (typeof value !== 'string') return null
|
||||
const trimmed = value.replace(/ /g, ' ').trim().replace(/\s+/g, ' ')
|
||||
return trimmed || null
|
||||
}
|
||||
|
||||
// 腾讯元宝 (yuanbao.tencent.com) 没暴露用户信息 API,初始化也不会动态拉取。
|
||||
// 但元宝用稳定的 BEM 命名(不是 CSS module hash),从侧栏用户块读:
|
||||
// - 用户名: .yb-nav__user .nick-info-name (<p>)
|
||||
// - 头像: .yb-nav__user .yb-common-nav__ft__avatar img
|
||||
// probe 通常在隐藏窗口里、刚 navigation 就跑,React 还没把侧栏渲染出来,
|
||||
// 所以这里要 poll 一下 DOM 直到 .yb-nav__user 渲染出且 .nick-info-name 有文本。
|
||||
export async function readYuanbaoAccountIdentity(
|
||||
webContents: WebContents | null | undefined,
|
||||
): Promise<YuanbaoAccountIdentityHints> {
|
||||
if (!webContents || webContents.isDestroyed()) {
|
||||
return { displayName: null, avatarUrl: null }
|
||||
}
|
||||
|
||||
const result = await webContents
|
||||
.executeJavaScript(
|
||||
`(async () => {
|
||||
try {
|
||||
const normalize = (value) => {
|
||||
if (typeof value !== 'string') return null;
|
||||
const trimmed = value.replace(/\\u00a0/g, ' ').trim();
|
||||
return trimmed || null;
|
||||
};
|
||||
const isVisible = (node) => {
|
||||
if (!(node instanceof HTMLElement)) return false;
|
||||
const style = window.getComputedStyle(node);
|
||||
if (
|
||||
style.display === 'none' ||
|
||||
style.visibility === 'hidden' ||
|
||||
style.opacity === '0' ||
|
||||
node.hidden
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const rect = node.getBoundingClientRect();
|
||||
return rect.width >= 4 && rect.height >= 4;
|
||||
};
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
const deadline = Date.now() + ${POLL_TIMEOUT_MS};
|
||||
let displayName = null;
|
||||
let avatarUrl = null;
|
||||
|
||||
while (Date.now() < deadline) {
|
||||
const root = document.querySelector('.yb-nav__user');
|
||||
if (root) {
|
||||
const nameNode = root.querySelector('.nick-info-name');
|
||||
if (isVisible(nameNode)) {
|
||||
const text = normalize(nameNode.textContent || '');
|
||||
if (text) displayName = text;
|
||||
}
|
||||
const avatarImg = root.querySelector('.yb-common-nav__ft__avatar img');
|
||||
if (avatarImg && isVisible(avatarImg)) {
|
||||
const src = normalize(avatarImg.src);
|
||||
if (src && !/(logo|icon|favicon)/i.test(src)) {
|
||||
avatarUrl = src;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (displayName && avatarUrl) break;
|
||||
await sleep(${POLL_INTERVAL_MS});
|
||||
}
|
||||
|
||||
return { displayName, avatarUrl };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})();`,
|
||||
true,
|
||||
)
|
||||
.catch(() => null)
|
||||
|
||||
if (!result || typeof result !== 'object') {
|
||||
return { displayName: null, avatarUrl: null }
|
||||
}
|
||||
|
||||
const typed = result as { displayName?: unknown; avatarUrl?: unknown }
|
||||
return {
|
||||
displayName: normalizeText(typed.displayName),
|
||||
avatarUrl: normalizeText(typed.avatarUrl),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user