feat(desktop/weixin-gzh): probe authenticated console with token resolver
Naked /cgi-bin/home redirects to the login page even with a live cookie, which made probe/refresh/verify mis-classify accounts as expired. Resolve the tokenized console URL via mp.weixin.qq.com first, then read the page state to detect 登录超时/未登录 banners and reuse detectWeixinGzh to confirm identity. Open-console also now lands on the tokenized URL. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -88,6 +88,12 @@ interface ActiveBindOperation {
|
||||
promise: Promise<DesktopAccountInfo>;
|
||||
}
|
||||
|
||||
interface WeixinGzhConsoleState {
|
||||
loggedOut: boolean;
|
||||
title: string;
|
||||
textSnippet: string;
|
||||
}
|
||||
|
||||
type ToutiaoMediaInfoResponse = {
|
||||
data?: {
|
||||
user?: {
|
||||
@@ -242,6 +248,8 @@ const MAX_CONCURRENT_BIND_WINDOWS = 2;
|
||||
const TOUTIAO_LOGIN_URL = "https://mp.toutiao.com/auth/page/login";
|
||||
const TOUTIAO_CONSOLE_HOME_URL = "https://mp.toutiao.com/profile_v4/index";
|
||||
const TOUTIAO_WORKBENCH_URL_PATTERN = /^https:\/\/mp\.toutiao\.com\/profile_v4\//i;
|
||||
const WEIXIN_GZH_HOME_URL = "https://mp.weixin.qq.com/";
|
||||
const WEIXIN_GZH_CONSOLE_HOME_URL = "https://mp.weixin.qq.com/cgi-bin/home";
|
||||
const BILIBILI_API_ORIGIN = "https://api.bilibili.com";
|
||||
const ZOL_API_ORIGIN = "https://open-api.zol.com.cn";
|
||||
const ZOL_REFERER = "https://post.zol.com.cn/";
|
||||
@@ -435,6 +443,87 @@ function extractToutiaoAccount(response: ToutiaoMediaInfoResponse | null | undef
|
||||
return candidates.find((item) => item !== null) ?? null;
|
||||
}
|
||||
|
||||
function extractWeixinGzhToken(html: string): string {
|
||||
return html.match(/data:\s*\{[\s\S]*?t:\s*["']([^"']+)["']/)?.[1] ?? "";
|
||||
}
|
||||
|
||||
function isWeixinGzhLoggedOutText(text: string): boolean {
|
||||
return /(登录超时|登录态超时|登录已过期|会话已过期|请重新登录|未登录)/.test(text);
|
||||
}
|
||||
|
||||
async function readWeixinGzhConsoleState(
|
||||
webContents: WebContents | null | undefined,
|
||||
): Promise<WeixinGzhConsoleState | null> {
|
||||
if (!webContents || webContents.isDestroyed()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const state = await webContents.executeJavaScript(
|
||||
`(() => {
|
||||
try {
|
||||
const text = String(document.body?.innerText || "");
|
||||
const title = String(document.title || "");
|
||||
return {
|
||||
title,
|
||||
textSnippet: text.slice(0, 500),
|
||||
loggedOut: /(登录超时|登录态超时|登录已过期|会话已过期|请重新登录|未登录)/.test(text + "\\n" + title),
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})();`,
|
||||
true,
|
||||
).catch(() => null);
|
||||
|
||||
if (!state || typeof state !== "object") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const typed = state as Partial<WeixinGzhConsoleState>;
|
||||
return {
|
||||
loggedOut:
|
||||
typed.loggedOut === true
|
||||
|| isWeixinGzhLoggedOutText(`${typed.title ?? ""}\n${typed.textSnippet ?? ""}`),
|
||||
title: typeof typed.title === "string" ? typed.title : "",
|
||||
textSnippet: typeof typed.textSnippet === "string" ? typed.textSnippet : "",
|
||||
};
|
||||
}
|
||||
|
||||
async function resolveWeixinGzhConsoleURL(session: Session): Promise<string> {
|
||||
const html = await sessionFetchText(
|
||||
session,
|
||||
WEIXIN_GZH_HOME_URL,
|
||||
{
|
||||
credentials: "include",
|
||||
headers: {
|
||||
accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||||
referer: WEIXIN_GZH_HOME_URL,
|
||||
},
|
||||
},
|
||||
).catch(() => "");
|
||||
|
||||
const token = extractWeixinGzhToken(html);
|
||||
if (!token) {
|
||||
return WEIXIN_GZH_HOME_URL;
|
||||
}
|
||||
|
||||
const url = new URL(WEIXIN_GZH_CONSOLE_HOME_URL);
|
||||
url.searchParams.set("t", "home/index");
|
||||
url.searchParams.set("lang", "zh_CN");
|
||||
url.searchParams.set("token", token);
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
async function resolvePublishAccountConsoleURL(
|
||||
definition: PublishPlatformBindingDefinition,
|
||||
session: Session,
|
||||
): Promise<string> {
|
||||
if (definition.id === "weixin_gzh") {
|
||||
return await resolveWeixinGzhConsoleURL(session);
|
||||
}
|
||||
return definition.consoleUrl;
|
||||
}
|
||||
|
||||
async function detectToutiaoFromPageState(
|
||||
webContents: WebContents,
|
||||
): Promise<Pick<DetectedAccount, "platformUid" | "displayName" | "avatarUrl"> | null> {
|
||||
@@ -1609,7 +1698,7 @@ async function resolvePublishAccountProfileFromHandle(
|
||||
if (isAIPlatformBinding(account.platform)) {
|
||||
const window = createBoundWindow(
|
||||
`${definition.label} 静默识别`,
|
||||
definition.consoleUrl,
|
||||
await resolvePublishAccountConsoleURL(definition, handle.session),
|
||||
handle.session,
|
||||
{ show: false },
|
||||
);
|
||||
@@ -1673,7 +1762,7 @@ export async function probePublishAccountSession(
|
||||
|
||||
const window = createBoundWindow(
|
||||
`${definition.label} 静默探针`,
|
||||
definition.consoleUrl,
|
||||
await resolvePublishAccountConsoleURL(definition, handle.session),
|
||||
handle.session,
|
||||
{ show: false },
|
||||
);
|
||||
@@ -1749,6 +1838,62 @@ export async function probePublishAccountSession(
|
||||
};
|
||||
}
|
||||
|
||||
if (account.platform === "weixin_gzh") {
|
||||
const pageState = await readWeixinGzhConsoleState(window.webContents);
|
||||
if (pageState?.loggedOut) {
|
||||
return {
|
||||
verdict: "expired",
|
||||
reason: "login_redirect",
|
||||
profile: null,
|
||||
sessionFingerprint: null,
|
||||
evidence: {
|
||||
url: currentURL,
|
||||
title: pageState.title,
|
||||
text: pageState.textSnippet,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const detected = await detectWeixinGzh({
|
||||
session: handle.session,
|
||||
webContents: window.webContents,
|
||||
}).catch(() => null);
|
||||
if (!detected) {
|
||||
return {
|
||||
verdict: "expired",
|
||||
reason: "login_redirect",
|
||||
profile: null,
|
||||
sessionFingerprint: null,
|
||||
evidence: {
|
||||
url: currentURL,
|
||||
title: pageState?.title,
|
||||
text: pageState?.textSnippet,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (!detectedAccountMatchesIdentity(detected, account)) {
|
||||
return {
|
||||
verdict: "expired",
|
||||
reason: "login_redirect",
|
||||
profile: null,
|
||||
sessionFingerprint: sanitizeDetectedAccount(detected).platformUid,
|
||||
evidence: {
|
||||
url: currentURL,
|
||||
detectedPlatformUid: detected.platformUid,
|
||||
expectedPlatformUid: account.platformUid,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
verdict: "active",
|
||||
reason: "probe_success",
|
||||
profile: toAccountHealthProfile(toPublishAccountProfile(detected)),
|
||||
sessionFingerprint: sanitizeDetectedAccount(detected).platformUid,
|
||||
};
|
||||
}
|
||||
|
||||
if (account.platform === "toutiaohao") {
|
||||
const detected = await detectToutiaoFromSession(handle.session);
|
||||
if (detected) {
|
||||
@@ -1954,7 +2099,7 @@ export async function silentRefreshPublishAccountSession(
|
||||
|
||||
const window = createBoundWindow(
|
||||
`${definition.label} 静默续期`,
|
||||
definition.consoleUrl,
|
||||
await resolvePublishAccountConsoleURL(definition, handle.session),
|
||||
handle.session,
|
||||
{ show: false },
|
||||
);
|
||||
@@ -1967,6 +2112,19 @@ export async function silentRefreshPublishAccountSession(
|
||||
|
||||
await sleep(1_200);
|
||||
await flushSessionPersistence(handle.session);
|
||||
if (account.platform === "weixin_gzh") {
|
||||
const pageState = await readWeixinGzhConsoleState(window.webContents);
|
||||
if (pageState?.loggedOut) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const detected = await detectWeixinGzh({
|
||||
session: handle.session,
|
||||
webContents: window.webContents,
|
||||
}).catch(() => null);
|
||||
return detected ? detectedAccountMatchesIdentity(detected, account) : false;
|
||||
}
|
||||
|
||||
if (account.platform === "zol") {
|
||||
return await verifyZolConsoleAccess(window, handle.session, account);
|
||||
}
|
||||
@@ -2214,7 +2372,7 @@ async function verifyPublishAccountConsoleAccess(
|
||||
|
||||
const window = createBoundWindow(
|
||||
`${definition.label} 静默校验`,
|
||||
definition.consoleUrl,
|
||||
await resolvePublishAccountConsoleURL(definition, handle.session),
|
||||
handle.session,
|
||||
{ show: false },
|
||||
);
|
||||
@@ -2242,6 +2400,19 @@ async function verifyPublishAccountConsoleAccess(
|
||||
return detected !== null;
|
||||
}
|
||||
|
||||
if (account.platform === "weixin_gzh") {
|
||||
const pageState = await readWeixinGzhConsoleState(window.webContents);
|
||||
if (pageState?.loggedOut) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const detected = await detectWeixinGzh({
|
||||
session: handle.session,
|
||||
webContents: window.webContents,
|
||||
}).catch(() => null);
|
||||
return detected ? detectedAccountMatchesIdentity(detected, account) : false;
|
||||
}
|
||||
|
||||
if (account.platform === "zol") {
|
||||
return await verifyZolConsoleAccess(window, handle.session, account);
|
||||
}
|
||||
@@ -2746,12 +2917,12 @@ async function detectSmzdm({ session, webContents }: DetectContext): Promise<Det
|
||||
}
|
||||
|
||||
async function detectWeixinGzh({ session }: DetectContext): Promise<DetectedAccount | null> {
|
||||
const html = await sessionFetchText(session, "https://mp.weixin.qq.com/").catch(() => "");
|
||||
const html = await sessionFetchText(session, WEIXIN_GZH_HOME_URL).catch(() => "");
|
||||
if (!html) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const token = html.match(/data:\s*\{[\s\S]*?t:\s*["']([^"']+)["']/)?.[1] ?? "";
|
||||
const token = extractWeixinGzhToken(html);
|
||||
const userName = html.match(/user_name:\s*["']([^"']+)["']/)?.[1] ?? "";
|
||||
const nickName = html.match(/nick_name:\s*["']([^"']+)["']/)?.[1] ?? "";
|
||||
const avatarFromHead = html.match(/class="weui-desktop-account__thumb"[^>]*src="([^"]+)"/)?.[1] ?? "";
|
||||
@@ -2895,7 +3066,7 @@ const publishBindingDefinitions: Record<string, PublishPlatformBindingDefinition
|
||||
id: "weixin_gzh",
|
||||
label: "微信公众号",
|
||||
loginUrl: "https://mp.weixin.qq.com/cgi-bin/loginpage",
|
||||
consoleUrl: "https://mp.weixin.qq.com/cgi-bin/home",
|
||||
consoleUrl: WEIXIN_GZH_CONSOLE_HOME_URL,
|
||||
detect: detectWeixinGzh,
|
||||
},
|
||||
zol: {
|
||||
@@ -3258,7 +3429,8 @@ export async function openPublishAccountConsole(account: PublishAccountIdentity)
|
||||
}
|
||||
|
||||
const handle = await ensurePublishAccountSessionHandle(account);
|
||||
const window = createBoundWindow(`${definition.label} 创作台`, definition.consoleUrl, handle.session);
|
||||
const consoleURL = await resolvePublishAccountConsoleURL(definition, handle.session);
|
||||
const window = createBoundWindow(`${definition.label} 创作台`, consoleURL, handle.session);
|
||||
window.show();
|
||||
window.focus();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user