fix(publish): harden zol login detection and content image upload
- Detect zol login redirects via passport/service hostnames and verify console access by re-detecting the bound account so a different logged in user is treated as expired instead of active. - Skip networkPic upload for local/private URLs and fall back to file upload (with siteType retry) so desktop-hosted assets no longer reach ZOL as unreachable links and produce blank images. - Surface zol_content_image_upload_failed and challenge errors instead of silently substituting empty image URLs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -60,6 +60,7 @@ interface PublishPlatformBindingDefinition {
|
||||
label: string;
|
||||
loginUrl: string;
|
||||
consoleUrl: string;
|
||||
loginRedirectUrls?: string[];
|
||||
detect(context: DetectContext): Promise<DetectedAccount | null>;
|
||||
}
|
||||
|
||||
@@ -242,6 +243,13 @@ 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 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/";
|
||||
const ZOL_CONSOLE_URL = "https://post.zol.com.cn/v2/manage/works/all";
|
||||
const ZOL_LOGIN_REDIRECT_URLS = [
|
||||
"https://passport.zol.com.cn/",
|
||||
"https://service.zol.com.cn/user/login",
|
||||
];
|
||||
const BILIBILI_WBI_MIXIN_KEY_ENC_TAB = [
|
||||
46, 47, 18, 2, 53, 8, 23, 32,
|
||||
15, 50, 10, 31, 58, 3, 45, 35,
|
||||
@@ -1729,7 +1737,7 @@ export async function probePublishAccountSession(
|
||||
};
|
||||
}
|
||||
|
||||
if (looksLikeLoginRedirect(currentURL, definition.loginUrl)) {
|
||||
if (definitionLooksLikeLoginRedirect(currentURL, definition)) {
|
||||
return {
|
||||
verdict: "expired",
|
||||
reason: "login_redirect",
|
||||
@@ -1810,7 +1818,7 @@ export async function probePublishAccountSession(
|
||||
};
|
||||
}
|
||||
|
||||
if (pageState.loggedOutSignalCount > 0 || looksLikeLoginRedirect(currentURL, definition.loginUrl)) {
|
||||
if (pageState.loggedOutSignalCount > 0 || definitionLooksLikeLoginRedirect(currentURL, definition)) {
|
||||
return {
|
||||
verdict: "expired",
|
||||
reason: "login_redirect",
|
||||
@@ -1868,6 +1876,19 @@ export async function probePublishAccountSession(
|
||||
webContents: window.webContents,
|
||||
});
|
||||
if (detected) {
|
||||
if (account.platform === "zol" && !detectedAccountMatchesIdentity(detected, account)) {
|
||||
return {
|
||||
verdict: "expired",
|
||||
reason: "login_redirect",
|
||||
profile: null,
|
||||
sessionFingerprint: sanitizeDetectedAccount(detected).platformUid,
|
||||
evidence: {
|
||||
detectedPlatformUid: detected.platformUid,
|
||||
expectedPlatformUid: account.platformUid,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
verdict: "active",
|
||||
reason: "probe_success",
|
||||
@@ -1876,7 +1897,9 @@ export async function probePublishAccountSession(
|
||||
};
|
||||
}
|
||||
|
||||
const consoleReady = await verifyPublishAccountConsoleAccess(account, handle);
|
||||
const consoleReady = account.platform === "zol"
|
||||
? await verifyZolConsoleAccess(window, handle.session, account)
|
||||
: await verifyPublishAccountConsoleAccess(account, handle);
|
||||
if (consoleReady) {
|
||||
return {
|
||||
verdict: "active",
|
||||
@@ -1944,7 +1967,11 @@ export async function silentRefreshPublishAccountSession(
|
||||
|
||||
await sleep(1_200);
|
||||
await flushSessionPersistence(handle.session);
|
||||
return !looksLikeLoginRedirect(window.webContents.getURL(), definition.loginUrl);
|
||||
if (account.platform === "zol") {
|
||||
return await verifyZolConsoleAccess(window, handle.session, account);
|
||||
}
|
||||
|
||||
return !definitionLooksLikeLoginRedirect(window.webContents.getURL(), definition);
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
@@ -2112,6 +2139,37 @@ async function verifyToutiaoConsoleAccess(window: BrowserWindow, session: Sessio
|
||||
return extractToutiaoAccount(sessionResponse) !== null;
|
||||
}
|
||||
|
||||
function detectedAccountMatchesIdentity(
|
||||
detected: DetectedAccount,
|
||||
account: PublishAccountIdentity,
|
||||
): boolean {
|
||||
const expected = normalizeAccountPlatformUid(account.platformUid) || account.platformUid;
|
||||
if (!expected) {
|
||||
return true;
|
||||
}
|
||||
return sameAccountPlatformUid(detected.platformUid, expected);
|
||||
}
|
||||
|
||||
async function verifyZolConsoleAccess(
|
||||
window: BrowserWindow,
|
||||
session: Session,
|
||||
account: PublishAccountIdentity,
|
||||
): Promise<boolean> {
|
||||
if (window.isDestroyed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const detected = await detectZol({
|
||||
session,
|
||||
webContents: window.webContents,
|
||||
}).catch(() => null);
|
||||
return detected ? detectedAccountMatchesIdentity(detected, account) : false;
|
||||
}
|
||||
|
||||
const LOGIN_REDIRECT_TOKEN_RE =
|
||||
/(^|[/?#=&._-])(login|signin|sign-in|sign_in|signup|sign-up|register|auth|oauth|passport|scan|qrcode|qrlogin|wxlogin|sso)(?=$|[/?#=&._-])/i;
|
||||
const LOGIN_REDIRECT_HOST_RE = /(^|[.-])(login|passport|auth|sso)(?=$|[.-])/i;
|
||||
|
||||
function looksLikeLoginRedirect(currentURL: string, loginURL: string): boolean {
|
||||
if (!currentURL || !loginURL) {
|
||||
return false;
|
||||
@@ -2127,8 +2185,8 @@ function looksLikeLoginRedirect(currentURL: string, loginURL: string): boolean {
|
||||
const currentPath = normalizeURLPath(current.pathname);
|
||||
const loginPath = normalizeURLPath(login.pathname);
|
||||
if (!loginPath) {
|
||||
return /(^|[/?#=&._-])(login|signin|sign-in|signup|sign-up|register|auth|oauth|passport|scan|qrcode|qrlogin|wxlogin|sso)(?=$|[/?#=&._-])/i
|
||||
.test(`${current.pathname}${current.search}${current.hash}`);
|
||||
return LOGIN_REDIRECT_TOKEN_RE.test(`${current.pathname}${current.search}${current.hash}`)
|
||||
|| LOGIN_REDIRECT_HOST_RE.test(current.hostname);
|
||||
}
|
||||
|
||||
return currentPath === loginPath || currentPath.startsWith(`${loginPath}/`);
|
||||
@@ -2137,6 +2195,14 @@ function looksLikeLoginRedirect(currentURL: string, loginURL: string): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
function definitionLooksLikeLoginRedirect(
|
||||
currentURL: string,
|
||||
definition: PublishPlatformBindingDefinition,
|
||||
): boolean {
|
||||
const loginRedirectUrls = definition.loginRedirectUrls ?? [definition.loginUrl];
|
||||
return loginRedirectUrls.some((loginURL) => looksLikeLoginRedirect(currentURL, loginURL));
|
||||
}
|
||||
|
||||
async function verifyPublishAccountConsoleAccess(
|
||||
account: PublishAccountIdentity,
|
||||
handle: SessionHandle,
|
||||
@@ -2176,7 +2242,11 @@ async function verifyPublishAccountConsoleAccess(
|
||||
return detected !== null;
|
||||
}
|
||||
|
||||
return !looksLikeLoginRedirect(currentURL, definition.loginUrl);
|
||||
if (account.platform === "zol") {
|
||||
return await verifyZolConsoleAccess(window, handle.session, account);
|
||||
}
|
||||
|
||||
return !definitionLooksLikeLoginRedirect(currentURL, definition);
|
||||
} finally {
|
||||
if (!window.isDestroyed()) {
|
||||
window.destroy();
|
||||
@@ -2699,14 +2769,23 @@ async function detectWeixinGzh({ session }: DetectContext): Promise<DetectedAcco
|
||||
};
|
||||
}
|
||||
|
||||
async function zolRequestHeaders(session: Session): Promise<Record<string, string>> {
|
||||
const cookie = await sessionCookieHeader(session, "zol.com.cn").catch(() => "");
|
||||
return {
|
||||
accept: "application/json, text/plain, */*",
|
||||
origin: "https://post.zol.com.cn",
|
||||
referer: ZOL_REFERER,
|
||||
...(cookie ? { cookie } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
async function detectZol({ session }: DetectContext): Promise<DetectedAccount | null> {
|
||||
const response = await sessionFetchJson<ZolUserInfoResponse>(
|
||||
session,
|
||||
"https://open-api.zol.com.cn/api/v1/creator.user.getinfo",
|
||||
`${ZOL_API_ORIGIN}/api/v1/creator.user.getinfo`,
|
||||
{
|
||||
headers: {
|
||||
accept: "application/json, text/plain, */*",
|
||||
},
|
||||
credentials: "include",
|
||||
headers: await zolRequestHeaders(session),
|
||||
},
|
||||
).catch(() => null);
|
||||
|
||||
@@ -2822,8 +2901,9 @@ const publishBindingDefinitions: Record<string, PublishPlatformBindingDefinition
|
||||
zol: {
|
||||
id: "zol",
|
||||
label: "中关村在线",
|
||||
loginUrl: "https://post.zol.com.cn/v2/manage/works/all",
|
||||
consoleUrl: "https://post.zol.com.cn/v2/manage/works/all",
|
||||
loginUrl: ZOL_CONSOLE_URL,
|
||||
consoleUrl: ZOL_CONSOLE_URL,
|
||||
loginRedirectUrls: ZOL_LOGIN_REDIRECT_URLS,
|
||||
detect: detectZol,
|
||||
},
|
||||
dongchedi: {
|
||||
|
||||
Reference in New Issue
Block a user